From 545e647d7b01b04b86133a7ec519ce3229aeeeee Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 10 Jun 2024 11:31:36 +0300 Subject: [PATCH 01/65] [#4] infrastructure and sample Client Cut Signed-off-by: Pavel Gross --- FrostFS.SDK.sln | 7 +- README.md | 77 ++++- src/FrostFS.SDK.ClientV2/Client.cs | 10 +- src/FrostFS.SDK.ClientV2/Extensions/Object.cs | 44 +++ .../FrostFS.SDK.ClientV2.csproj | 1 + .../Interfaces/IFrostFSClient.cs | 13 +- .../Mappers/GRPC/ContainerId.cs | 3 +- .../Mappers/GRPC/Object.cs | 92 ++++- .../Mappers/GRPC/ObjectId.cs | 5 + src/FrostFS.SDK.ClientV2/RequestSigner.cs | 2 +- .../Services/Container.cs | 8 +- src/FrostFS.SDK.ClientV2/Services/Object.cs | 317 ++++++++++++------ src/FrostFS.SDK.Cryptography/Base58.cs | 2 - src/FrostFS.SDK.Cryptography/Helper.cs | 1 - src/FrostFS.SDK.ModelsV2/ContainerId.cs | 28 +- src/FrostFS.SDK.ModelsV2/Object/Object.cs | 65 ++++ .../Object/ObjectAttribute.cs | 13 + .../{Object.cs => Object/ObjectFilter.cs} | 39 --- .../Object/ObjectHeader.cs | 37 ++ src/FrostFS.SDK.ModelsV2/Splitter.cs | 88 +++++ .../object/Extension.Message.cs | 56 ++++ .../session/Extension.XHeader.cs | 2 +- 22 files changed, 717 insertions(+), 193 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Extensions/Object.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Object/Object.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs rename src/FrostFS.SDK.ModelsV2/{Object.cs => Object/ObjectFilter.cs} (56%) create mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Splitter.cs diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index d7d789e..67e5eb3 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -29,9 +29,8 @@ Global {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.Build.0 = Release|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B738F3E1-654D-41A3-B068-58ED122BB688}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 832b42f..542a544 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; -var fsClient = new Client(, ); +var fsClient = Client.GetInstance(, ); // List containers var containersIds = await fsClient.ListContainersAsync(); @@ -55,7 +55,7 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; -var fsClient = new Client(, ); +var fsClient = Client.GetInstance(, ); // Search regular objects var objectsIds = await fsClient.SearchObjectAsync( @@ -77,4 +77,77 @@ var objHeader = await fsClient.GetObjectHeadAsync(cId, oId); // Get object var obj = await fsClient.GetObjectAsync(cId, oId); +``` + +### Custom client cut +```csharp + +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Extensions; +using FrostFS.SDK.ClientV2.Interfaces; + +var fsClient = Client.GetInstance(, ); + +ContainerId containerId = ; +string fileName = ; + +await PutObjectClientCut(fsClient, containerId, fileName); + +static async Task PutObjectClientCut(IFrostFSClient fsClient, ContainerId containerId, string fileName) +{ + List sentObjectIds = []; + FrostFS.SDK.ModelsV2.Object? currentObject; + + var partSize = 1024 * 1024; + var buffer = new byte[partSize]; + + var largeObject = new LargeObject(containerId); + + var split = new Split(); + + var fileInfo = new FileInfo(fileName); + var fullLength = (ulong)fileInfo.Length; + var fileNameAttribute = new ObjectAttribute("fileName", fileInfo.Name); + + using var stream = File.OpenRead(fileName); + while (true) + { + var bytesCount = await stream.ReadAsync(buffer.AsMemory(0, partSize)); + + split.Previous = sentObjectIds.LastOrDefault(); + + largeObject.AppendBlock(buffer, bytesCount); + + currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, buffer) + .AddAttribute(fileNameAttribute) + .SetSplit(split); + + if (largeObject.PayloadLength == fullLength) + break; + + var objectId = await fsClient.PutSingleObjectAsync(currentObject); + sentObjectIds.Add(objectId); + } + + if (sentObjectIds.Any()) + { + largeObject.CalculateHash(); + + var linkObject = new LinkObject(containerId, split.SplitId, largeObject) + .AddChildren(sentObjectIds); + + _ = await fsClient.PutSingleObjectAsync(linkObject); + + currentObject.SetParent(largeObject); + _ = await fsClient.PutSingleObjectAsync(currentObject); + + return currentObject.GetParentId(); + } + + return await fsClient.PutSingleObjectAsync(currentObject); +} + ``` \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 7cb47d6..7e9e29c 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,5 +1,6 @@ using System; using System.Security.Cryptography; +using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; @@ -25,7 +26,12 @@ public partial class Client: IFrostFSClient private ObjectService.ObjectServiceClient? _objectServiceClient; private SessionService.SessionServiceClient? _sessionServiceClient; - public Client(string key, string host) + public static IFrostFSClient GetInstance(string key, string host) + { + return new Client(key, host); + } + + private Client(string key, string host) { // TODO: Развязать клиент и реализацию GRPC _key = key.LoadWif(); @@ -37,7 +43,7 @@ public partial class Client: IFrostFSClient InitSessionClient(); CheckFrostFsVersionSupport(); } - + private async void CheckFrostFsVersionSupport() { var localNodeInfo = await GetLocalNodeInfoAsync(); diff --git a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs new file mode 100644 index 0000000..93d8d5f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs @@ -0,0 +1,44 @@ + +using System.Collections.Generic; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Extensions; + +public static class Extensions +{ + public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) + { + obj.Header.PayloadLength = length; + return obj; + } + + public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, string key, string value) + { + obj.AddAttribute(new ObjectAttribute(key, value)); + return obj; + } + + public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, ObjectAttribute attribute) + { + obj.Header.Attributes.Add(attribute); + return obj; + } + + public static ModelsV2.Object AddAttributes(this ModelsV2.Object obj, IEnumerable attributes) + { + obj.Header.Attributes.AddRange(attributes); + return obj; + } + + public static ModelsV2.Object SetSplit(this ModelsV2.Object obj, Split split) + { + obj.Header.Split = split; + return obj; + } + + public static LinkObject AddChildren(this LinkObject linkObject, IEnumerable objectIds) + { + linkObject.Header.Split.Children.AddRange(objectIds); + return linkObject; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index 0208178..d446e0e 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -8,6 +8,7 @@ + diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 8412c6c..5b82d1b 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -9,13 +9,24 @@ namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient { Task GetContainerAsync(ContainerId containerId); + IAsyncEnumerable ListContainersAsync(); + Task CreateContainerAsync(ModelsV2.Container container); + Task DeleteContainerAsync(ContainerId containerId); + Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId); + Task PutObjectAsync(ObjectHeader header, Stream payload); + Task PutObjectAsync(ObjectHeader header, byte[] payload); + + Task PutSingleObjectAsync(ModelsV2.Object obj); + Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId); - IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); + + IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs index 1bfd614..920c49a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs @@ -1,4 +1,5 @@ using FrostFS.Refs; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; @@ -10,7 +11,7 @@ public static class ContainerIdMapper { return new ContainerID { - Value = ByteString.CopyFrom(containerId.ToHash()) + Value = ByteString.CopyFrom(Base58.Decode(containerId.Value)) }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs index b6e68f6..ef21202 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs @@ -1,8 +1,9 @@ using System; using System.Linq; - using FrostFS.Object; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using Google.Protobuf; using MatchType = FrostFS.Object.MatchType; using ObjectType = FrostFS.Object.ObjectType; @@ -29,11 +30,20 @@ public static class ObjectFilterMapper { public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) { - var objMatchTypeName = Enum.GetName(typeof(MatchType), filter.MatchType) - ?? throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'."); + var objMatchTypeName = filter.MatchType switch + { + ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, + ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, + ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, + ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, + ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, + + _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") + }; + return new SearchRequest.Types.Body.Types.Filter { - MatchType = (MatchType)Enum.Parse(typeof(MatchType), objMatchTypeName), + MatchType = objMatchTypeName, Key = filter.Key, Value = filter.Value }; @@ -44,13 +54,19 @@ public static class ObjectHeaderMapper { public static Header ToGrpcMessage(this ObjectHeader header) { - var objTypeName = Enum.GetName(typeof(ObjectType), header.ObjectType) - ?? throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'."); - var head = new Header + var objTypeName = header.ObjectType switch { - Attributes = { }, + ModelsV2.Enums.ObjectType.Regular => ObjectType.Regular, + ModelsV2.Enums.ObjectType.Lock => ObjectType.Lock, + ModelsV2.Enums.ObjectType.Tombstone => ObjectType.Tombstone, + _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") + }; + + var head = new Header + { ContainerId = header.ContainerId.ToGrpcMessage(), - ObjectType = (ObjectType)Enum.Parse(typeof(ObjectType), objTypeName) + ObjectType = objTypeName, + PayloadLength = header.PayloadLength }; foreach (var attribute in header.Attributes) @@ -58,20 +74,42 @@ public static class ObjectHeaderMapper head.Attributes.Add(attribute.ToGrpcMessage()); } + var split = header.Split; + if (split != null) + { + head.Split = new Header.Types.Split + { + Parent = split.Parent?.ToGrpcMessage(), + ParentSignature = split.ParentSignature?.ToGrpcMessage(), + ParentHeader = split.ParentHeader?.ToGrpcMessage(), + Previous = split.Previous?.ToGrpcMessage(), + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Any()) + head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + } + return head; } public static ObjectHeader ToModel(this Header header) { - var objTypeName = Enum.GetName(typeof(ModelsV2.Enums.ObjectType), header.ObjectType) - ?? throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'."); + var objTypeName = header.ObjectType switch + { + ObjectType.Regular => ModelsV2.Enums.ObjectType.Regular, + ObjectType.Lock => ModelsV2.Enums.ObjectType.Lock, + ObjectType.Tombstone => ModelsV2.Enums.ObjectType.Tombstone, + _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") + }; + return new ObjectHeader( - ContainerId.FromHash(header.ContainerId.Value.ToByteArray()), - (ModelsV2.Enums.ObjectType)Enum.Parse(typeof(ModelsV2.Enums.ObjectType), objTypeName), + new ContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), + objTypeName, header.Attributes.Select(attribute => attribute.ToModel()).ToArray() ) { - Size = (long)header.PayloadLength, + PayloadLength = header.PayloadLength, Version = header.Version.ToModel() }; } @@ -81,11 +119,33 @@ public static class ObjectMapper { public static ModelsV2.Object ToModel(this Object.Object obj) { - return new ModelsV2.Object + return new ModelsV2.Object() { Header = obj.Header.ToModel(), ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), Payload = obj.Payload.ToByteArray() }; + } +} + +public static class SignatureMapper +{ + public static Refs.Signature ToGrpcMessage(this Signature signature) + { + var scheme = signature.Scheme switch + { + SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, + SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, + SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, + _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) + }; + + return new Refs.Signature + { + Key = ByteString.CopyFrom(signature.Key), + Scheme = scheme, + Sign = ByteString.CopyFrom(signature.Sign) + }; } -} \ No newline at end of file +} + diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs index 4c28035..bfd568d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs @@ -13,4 +13,9 @@ public static class ObjectIdMapper Value = ByteString.CopyFrom(objectId.ToHash()) }; } + + public static ObjectId ToModel(this ObjectID objectId) + { + return ObjectId.FromHash(objectId.Value.ToByteArray()); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/RequestSigner.cs index 3747c92..23f8eb6 100644 --- a/src/FrostFS.SDK.ClientV2/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/RequestSigner.cs @@ -91,7 +91,7 @@ public static class RequestSigner { IRequest => new RequestVerificationHeader(), IResponse => new ResponseVerificationHeader(), - _ => throw new InvalidOperationException("Unsopported message type") + _ => throw new InvalidOperationException("Unsupported message type") }; var verifyOrigin = message.GetVerificationHeader(); diff --git a/src/FrostFS.SDK.ClientV2/Services/Container.cs b/src/FrostFS.SDK.ClientV2/Services/Container.cs index 886768c..7566492 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Container.cs @@ -1,5 +1,6 @@ using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using System.Collections.Generic; using System.Threading.Tasks; @@ -17,6 +18,7 @@ public partial class Client ContainerId = cid.ToGrpcMessage() }, }; + request.AddMetaHeader(); request.Sign(_key); var response = await _containerServiceClient.GetAsync(request); @@ -33,13 +35,14 @@ public partial class Client OwnerId = OwnerId.ToGrpcMessage() } }; + request.AddMetaHeader(); request.Sign(_key); var response = await _containerServiceClient.ListAsync(request); Verifier.CheckResponse(response); foreach (var cid in response.Body.ContainerIds) { - yield return ContainerId.FromHash(cid.Value.ToByteArray()); + yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); } } @@ -48,6 +51,7 @@ public partial class Client var cntnr = container.ToGrpcMessage(); cntnr.OwnerId = OwnerId.ToGrpcMessage(); cntnr.Version = Version.ToGrpcMessage(); + var request = new PutRequest { Body = new PutRequest.Types.Body @@ -60,7 +64,7 @@ public partial class Client request.Sign(_key); var response = await _containerServiceClient.PutAsync(request); Verifier.CheckResponse(response); - return ContainerId.FromHash(response.Body.ContainerId.Value.ToByteArray()); + return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); } public async Task DeleteContainerAsync(ContainerId cid) diff --git a/src/FrostFS.SDK.ClientV2/Services/Object.cs b/src/FrostFS.SDK.ClientV2/Services/Object.cs index 180e351..bc98dc8 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Object.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using Google.Protobuf; @@ -9,9 +10,10 @@ using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; using FrostFS.Session; +using FrostFS.SDK.ModelsV2; + namespace FrostFS.SDK.ClientV2; public partial class Client @@ -20,7 +22,7 @@ public partial class Client { var request = new HeadRequest { - Body = new HeadRequest.Types.Body + Body = new HeadRequest.Types.Body { Address = new Address { @@ -29,12 +31,12 @@ public partial class Client } } }; - + request.AddMetaHeader(); request.Sign(_key); - var response = await _objectServiceClient.HeadAsync(request); + var response = await _objectServiceClient!.HeadAsync(request); Verifier.CheckResponse(response); - + return response.Body.Header.Header.ToModel(); } @@ -45,7 +47,6 @@ public partial class Client { Body = new GetRequest.Types.Body { - Raw = false, Address = new Address { ContainerId = cid.ToGrpcMessage(), @@ -76,85 +77,8 @@ public partial class Client public async Task PutObjectAsync(ObjectHeader header, byte[] payload) { - return await PutObject(header, new MemoryStream(payload)); - } - - public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - Address = new Address - { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() - } - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient.DeleteAsync(request); - Verifier.CheckResponse(response); - } - - public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) - { - var request = new SearchRequest - { - Body = new SearchRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Filters = { }, - Version = 1 - } - }; - - foreach (var filter in filters) - { - request.Body.Filters.Add(filter.ToGrpcMessage()); - } - - request.AddMetaHeader(); - request.Sign(_key); - var objectsIds = SearchObjects(request); - - await foreach (var oid in objectsIds) - { - yield return ObjectId.FromHash(oid.Value.ToByteArray()); - } - } - - private async Task GetObject(GetRequest request) - { - using var stream = GetObjectInit(request); - var obj = await stream.ReadHeader(); - var payload = new byte[obj.Header.PayloadLength]; - var offset = 0; - var chunk = await stream.ReadChunk(); - - while (chunk is not null) - { - chunk.CopyTo(payload, offset); - offset += chunk.Length; - chunk = await stream.ReadChunk(); - } - - obj.Payload = ByteString.CopyFrom(payload); - - return obj; - } - - private ObjectReader GetObjectInit(GetRequest initRequest) - { - if (initRequest is null) - throw new ArgumentNullException(nameof(initRequest)); - - return new ObjectReader - { - Call = _objectServiceClient.Get(initRequest) - }; + using var stream = new MemoryStream(payload); + return await PutObject(header, stream); } private async Task PutObject(ObjectHeader header, Stream payload) @@ -163,12 +87,12 @@ public partial class Client var hdr = header.ToGrpcMessage(); hdr.OwnerId = OwnerId.ToGrpcMessage(); hdr.Version = Version.ToGrpcMessage(); - + var oid = new ObjectID { Value = hdr.Sha256() }; - + var request = new PutRequest { Body = new PutRequest.Types.Body @@ -188,23 +112,27 @@ public partial class Client ObjectSessionContext.Types.Verb.Put, _key ); - + request.Sign(_key); using var stream = await PutObjectInit(request); var buffer = new byte[Constants.ObjectChunkSize]; - var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); - - while (bufferLength > 0) + + while (true) { + var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); + + if (bufferLength == 0) + break; + request.Body = new PutRequest.Types.Body { Chunk = ByteString.CopyFrom(buffer[..bufferLength]), }; + request.VerifyHeader = null; request.Sign(_key); await stream.Write(request); - bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); } var response = await stream.Close(); @@ -213,6 +141,192 @@ public partial class Client return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } + public async Task PutSingleObjectAsync(ModelsV2.Object @object) + { + var sessionToken = await CreateSessionAsync(uint.MaxValue); + + var obj = CreateObject(@object); + + var request = new PutSingleRequest + { + Body = new () { Object = obj } + }; + + request.AddMetaHeader(); + request.AddObjectSessionToken( + sessionToken, + obj.Header.ContainerId, + obj.ObjectId, + ObjectSessionContext.Types.Verb.Put, + _key + ); + + request.Sign(_key); + + var response = await _objectServiceClient!.PutSingleAsync(request); + Verifier.CheckResponse(response); + + return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); + } + + public Object.Object CreateObject(ModelsV2.Object @object) + { + var grpcHeader = @object.Header.ToGrpcMessage(); + + grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); + grpcHeader.Version = Version.ToGrpcMessage(); + + if (@object.Payload != null) + { + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + } + + var split = @object.Header.Split; + if (split != null) + { + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Any()) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, []); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(_key.PublicKey()), + Sign = ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + } + + var obj = new Object.Object + { + Header = grpcHeader, + ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + Payload = ByteString.CopyFrom(@object.Payload) + }; + + obj.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(_key.PublicKey()), + Sign = ByteString.CopyFrom(_key.SignData(obj.ObjectId.ToByteArray())), + }; + + return obj; + } + + public Header CreateHeader(ObjectHeader header, byte[]? payload) + { + var grpcHeader = header.ToGrpcMessage(); + + grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); + grpcHeader.Version = Version.ToGrpcMessage(); + + if (header.PayloadCheckSum != null) + { + grpcHeader.PayloadHash = new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(header.PayloadCheckSum) + }; + } + else + { + if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + } + + return grpcHeader; + } + + public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + Address = new Address + { + ContainerId = cid.ToGrpcMessage(), + ObjectId = oid.ToGrpcMessage() + } + } + }; + + request.AddMetaHeader(); + request.Sign(_key); + var response = await _objectServiceClient!.DeleteAsync(request); + Verifier.CheckResponse(response); + } + + public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) + { + var request = new SearchRequest + { + Body = new SearchRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Filters = { }, + Version = 1 + } + }; + + foreach (var filter in filters) + { + request.Body.Filters.Add(filter.ToGrpcMessage()); + } + + request.AddMetaHeader(); + request.Sign(_key); + var objectsIds = SearchObjects(request); + + await foreach (var oid in objectsIds) + { + yield return ObjectId.FromHash(oid.Value.ToByteArray()); + } + } + + private async Task GetObject(GetRequest request) + { + using var stream = GetObjectInit(request); + var obj = await stream.ReadHeader(); + var payload = new byte[obj.Header.PayloadLength]; + var offset = 0; + var chunk = await stream.ReadChunk(); + + while (chunk is not null) + { + chunk.CopyTo(payload, offset); + offset += chunk.Length; + chunk = await stream.ReadChunk(); + } + + obj.Payload = ByteString.CopyFrom(payload); + + return obj; + } + + private ObjectReader GetObjectInit(GetRequest initRequest) + { + if (initRequest is null) + throw new ArgumentNullException(nameof(initRequest)); + + return new ObjectReader + { + Call = _objectServiceClient!.Get(initRequest) + }; + } + private async Task PutObjectInit(PutRequest initRequest) { if (initRequest is null) @@ -220,9 +334,9 @@ public partial class Client throw new ArgumentNullException(nameof(initRequest)); } - var call = _objectServiceClient.Put(); + var call = _objectServiceClient!.Put(); await call.RequestStream.WriteAsync(initRequest); - + return new ObjectStreamer(call); } @@ -236,7 +350,7 @@ public partial class Client { yield return oid; } - + ids = await stream.Read(); } } @@ -248,6 +362,17 @@ public partial class Client throw new ArgumentNullException(nameof(initRequest)); } - return new SearchReader(_objectServiceClient.Search(initRequest)); + return new SearchReader(_objectServiceClient!.Search(initRequest)); + } + + public Checksum Sha256Checksum(byte[] data) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(data.Sha256()) + }; } } + + diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 36711c0..d9d91cf 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Numerics; using System.Text; -using Org.BouncyCastle.Security.Certificates; - namespace FrostFS.SDK.Cryptography; public static class Base58 diff --git a/src/FrostFS.SDK.Cryptography/Helper.cs b/src/FrostFS.SDK.Cryptography/Helper.cs index 93058d9..266a193 100644 --- a/src/FrostFS.SDK.Cryptography/Helper.cs +++ b/src/FrostFS.SDK.Cryptography/Helper.cs @@ -44,7 +44,6 @@ public static class Helper { return ByteString.CopyFrom(data.ToByteArray().Sha256()); } - public static ulong Murmur64(this byte[] value, uint seed) { diff --git a/src/FrostFS.SDK.ModelsV2/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/ContainerId.cs index 8bfbc54..349eb1c 100644 --- a/src/FrostFS.SDK.ModelsV2/ContainerId.cs +++ b/src/FrostFS.SDK.ModelsV2/ContainerId.cs @@ -1,30 +1,8 @@ -using System; +namespace FrostFS.SDK.ModelsV2; -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK.ModelsV2; - -public class ContainerId +public class ContainerId(string id) { - public string Value { get; set; } - - public ContainerId(string id) - { - Value = id; - } - - public static ContainerId FromHash(byte[] hash) - { - if (hash.Length != Constants.Sha256HashLength) - throw new FormatException("ContainerID must be a sha256 hash."); - - return new ContainerId(Base58.Encode(hash)); - } - - public byte[] ToHash() - { - return Base58.Decode(Value); - } + public string Value { get; set; } = id; public override string ToString() { diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs new file mode 100644 index 0000000..5810699 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -0,0 +1,65 @@ +using System.Security.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; + +namespace FrostFS.SDK.ModelsV2; + +public class Object +{ + public Object() + { + } + + public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) + { + Payload = payload; + Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); + } + + public ObjectHeader Header { get; set; } + public ObjectId ObjectId { get; set; } + public byte[] Payload { get; set; } + public Signature Signature { get; set; } + + public void SetParent(LargeObject largeObject) + { + Header.Split!.ParentHeader = largeObject.Header; + } + + public ObjectId? GetParentId() + { + return Header.Split?.Parent; + } +} + +public class LargeObject(ContainerId container) : Object(container, []) +{ + private SHA256 payloadHash = SHA256.Create(); + + public void AppendBlock(byte[] bytes, int count) + { + Header.PayloadLength += (ulong)count; + this.payloadHash.TransformBlock(bytes, 0, count, bytes, 0); + } + + public void CalculateHash() + { + this.payloadHash.TransformFinalBlock([], 0, 0); + Header.PayloadCheckSum = this.payloadHash.Hash; + } + + public ulong PayloadLength + { + get { return Header.PayloadLength; } + } +} + +public class LinkObject : Object +{ + public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) + { + Header.Split = new Split(splitId) + { + ParentHeader = largeObject.Header + }; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs new file mode 100644 index 0000000..b114f2b --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs @@ -0,0 +1,13 @@ +namespace FrostFS.SDK.ModelsV2; + +public class ObjectAttribute +{ + public string Key { get; set; } + public string Value { get; set; } + + public ObjectAttribute(string key, string value) + { + Key = key; + Value = value; + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs similarity index 56% rename from src/FrostFS.SDK.ModelsV2/Object.cs rename to src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs index 7db6289..5fdb54f 100644 --- a/src/FrostFS.SDK.ModelsV2/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs @@ -2,18 +2,6 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class ObjectAttribute -{ - public string Key { get; set; } - public string Value { get; set; } - - public ObjectAttribute(string key, string value) - { - Key = key; - Value = value; - } -} - public class ObjectFilter { private const string HeaderPrefix = "$Object:"; @@ -48,30 +36,3 @@ public class ObjectFilter return new ObjectFilter(matchType, HeaderPrefix + "version", version.ToString()); } } - -public class ObjectHeader -{ - public ObjectAttribute[] Attributes { get; set; } - public ContainerId ContainerId { get; set; } - public long Size { get; set; } - public ObjectType ObjectType { get; set; } - public Version Version { get; set; } - - public ObjectHeader( - ContainerId containerId, - ObjectType type = ObjectType.Regular, - params ObjectAttribute[] attributes - ) - { - Attributes = attributes; - ContainerId = containerId; - ObjectType = type; - } -} - -public class Object -{ - public ObjectHeader Header { get; set; } - public ObjectId ObjectId { get; set; } - public byte[] Payload { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs new file mode 100644 index 0000000..8f51160 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +using FrostFS.SDK.ModelsV2.Enums; + +namespace FrostFS.SDK.ModelsV2; + +public class ObjectHeader +{ + public OwnerId OwnerId { get; set; } + + public List Attributes { get; set; } + + public ContainerId ContainerId { get; set; } + + public ulong PayloadLength { get; set; } + + public byte[]? PayloadCheckSum { get; set; } + + public ObjectType ObjectType { get; set; } + + public Version Version { get; set; } + + public Split? Split { get; set; } + + public bool ClientCut { get; set; } + + public ObjectHeader( + ContainerId containerId, + ObjectType type = ObjectType.Regular, + params ObjectAttribute[] attributes + ) + { + Attributes = [.. attributes]; + ContainerId = containerId; + ObjectType = type; + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Splitter.cs new file mode 100644 index 0000000..f48f903 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Splitter.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace FrostFS.SDK.ModelsV2; + +public class Split +{ + public Split() : this(new SplitId()) + { + } + + public Split(SplitId splitId) + { + SplitId = splitId; + } + + public SplitId SplitId { get; private set; } + + public ObjectId? Parent { get; set; } + + public ObjectId? Previous { get; set; } + + public Signature? ParentSignature { get; set; } + + public ObjectHeader? ParentHeader { get; set; } + + public List Children { get; } = []; +} + + public enum SignatureScheme { + EcdsaSha512, + EcdsaRfc6979Sha256, + EcdsaRfc6979Sha256WalletConnect + } + +public class Signature +{ + public byte[] Key { get; set; } + public byte[] Sign { get; set; } + public SignatureScheme Scheme { get; set; } +} + +public class SplitId +{ + private Guid id; + + public SplitId() + { + this.id = Guid.NewGuid(); + } + public SplitId(Guid guid) + { + this.id = guid; + } + + private SplitId(byte[] binary) + { + this.id = new Guid(binary); + } + + private SplitId(string str) + { + this.id = new Guid(str); + } + + public static SplitId CrateFromBinary(byte[] binaryData) + { + return new SplitId(binaryData); + } + + public static SplitId CrateFromString(string stringData) + { + return new SplitId(stringData); + } + + public override string ToString() + { + return this.id.ToString(); + } + + public byte[]? ToBinary() + { + if (this.id == Guid.Empty) + return null; + + return this.id.ToByteArray(); + } +} diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index 243ab00..6334c71 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -115,6 +115,62 @@ namespace FrostFS.Object } } + public partial class PutSingleRequest : IRequest + { + IMetaHeader IVerificableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerificableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } + + public partial class PutSingleResponse : IResponse + { + IMetaHeader IVerificableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerificableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } + public partial class DeleteRequest : IRequest { IMetaHeader IVerificableMessage.GetMetaHeader() diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs index f8f2695..c0c5b25 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs @@ -2,7 +2,7 @@ public partial class XHeader { - public const string ReservedXHeaderPrefix = "__NEOFS__"; + public const string ReservedXHeaderPrefix = "__SYSTEM__"; public const string XHeaderNetmapEpoch = ReservedXHeaderPrefix + "NETMAP_EPOCH"; public const string XHeaderNetmapLookupDepth = ReservedXHeaderPrefix + "NETMAP_LOOKUP_DEPTH"; } From b69d22966f0335d64946cdb3cfba94b042d5b9a8 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 14 Jun 2024 11:58:29 +0300 Subject: [PATCH 02/65] [#7] Client cut internal --- FrostFS.SDK.sln | 3 + README.md | 22 +++-- src/FrostFS.SDK.ClientV2/Client.cs | 40 +++++++- src/FrostFS.SDK.ClientV2/Extensions/Object.cs | 2 +- .../Interfaces/IFrostFSClient.cs | 8 +- .../Mappers/GRPC/Object.cs | 25 ++++- .../Mappers/GRPC/OwnerId.cs | 5 + src/FrostFS.SDK.ClientV2/Services/Object.cs | 99 +++++++++++++++++-- .../FrostFS.SDK.Cryptography.csproj | 2 +- src/FrostFS.SDK.Cryptography/Helper.cs | 1 + src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs | 2 +- src/FrostFS.SDK.ModelsV2/Object/Object.cs | 5 +- .../Object/ObjectHeader.cs | 4 +- .../object/Extension.Message.cs | 6 ++ .../FrostFS.SDK.Tests.csproj | 28 ++++++ 15 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index 67e5eb3..fcfe732 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -1,4 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 +# +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" diff --git a/README.md b/README.md index 542a544..89ab89a 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ static async Task PutObjectClientCut(IFrostFSClient fsClient, Contain var fileInfo = new FileInfo(fileName); var fullLength = (ulong)fileInfo.Length; var fileNameAttribute = new ObjectAttribute("fileName", fileInfo.Name); - + using var stream = File.OpenRead(fileName); while (true) { @@ -121,7 +121,7 @@ static async Task PutObjectClientCut(IFrostFSClient fsClient, Contain largeObject.AppendBlock(buffer, bytesCount); - currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, buffer) + currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) .AddAttribute(fileNameAttribute) .SetSplit(split); @@ -134,15 +134,19 @@ static async Task PutObjectClientCut(IFrostFSClient fsClient, Contain if (sentObjectIds.Any()) { - largeObject.CalculateHash(); - - var linkObject = new LinkObject(containerId, split.SplitId, largeObject) - .AddChildren(sentObjectIds); - - _ = await fsClient.PutSingleObjectAsync(linkObject); + largeObject.CalculateHash() + .AddAttribute(fileNameAttribute); currentObject.SetParent(largeObject); - _ = await fsClient.PutSingleObjectAsync(currentObject); + + var objectId = await fsClient.PutSingleObjectAsync(currentObject); + sentObjectIds.Add(objectId); + + var linkObject = new LinkObject(containerId, split.SplitId, largeObject) + .AddChildren(sentObjectIds) + .AddAttribute(fileNameAttribute); + + _ = await fsClient.PutSingleObjectAsync(linkObject); return currentObject.GetParentId(); } diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 7e9e29c..96fb227 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Netmap; @@ -10,6 +12,7 @@ using FrostFS.SDK.ModelsV2; using FrostFS.Session; using Grpc.Core; using Grpc.Net.Client; +using static FrostFS.Netmap.NetworkConfig.Types; using Version = FrostFS.SDK.ModelsV2.Version; namespace FrostFS.SDK.ClientV2; @@ -21,6 +24,8 @@ public partial class Client: IFrostFSClient public readonly OwnerId OwnerId; public readonly Version Version = new(2, 13); + private readonly Dictionary NetworkSettings = []; + private ContainerService.ContainerServiceClient? _containerServiceClient; private NetmapService.NetmapServiceClient? _netmapServiceClient; private ObjectService.ObjectServiceClient? _objectServiceClient; @@ -42,6 +47,33 @@ public partial class Client: IFrostFSClient InitObjectClient(); InitSessionClient(); CheckFrostFsVersionSupport(); + + InitNetworkInfoAsync(); + } + + private async void InitNetworkInfoAsync() + { + var info = await GetNetworkInfoAsync(); + + foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + { + SetNetworksParam(param); + } + } + + private void SetNetworksParam(Parameter param) + { + var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); + + var encodedValue = param.Value.ToByteArray(); + + ulong val = 0; + for (var i = encodedValue.Length - 1; i >= 0; i--) + { + val = (val << 8) + encodedValue[i]; + } + + NetworkSettings.Add(key, val); } private async void CheckFrostFsVersionSupport() @@ -84,7 +116,11 @@ public partial class Client: IFrostFSClient throw new ArgumentException(msg); } - _channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions { Credentials = grpcCredentials }); + _channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions + { + Credentials = grpcCredentials, + HttpHandler = new System.Net.Http.HttpClientHandler() + }); } private void InitContainerClient() @@ -106,4 +142,4 @@ public partial class Client: IFrostFSClient { _sessionServiceClient = new SessionService.SessionServiceClient(_channel); } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs index 93d8d5f..3765e71 100644 --- a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Extensions/Object.cs @@ -38,7 +38,7 @@ public static class Extensions public static LinkObject AddChildren(this LinkObject linkObject, IEnumerable objectIds) { - linkObject.Header.Split.Children.AddRange(objectIds); + linkObject.Header.Split!.Children.AddRange(objectIds); return linkObject; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 5b82d1b..9ae6833 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using FrostFS.SDK.ModelsV2; @@ -18,13 +19,14 @@ public interface IFrostFSClient Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId); - Task PutObjectAsync(ObjectHeader header, Stream payload); + Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default); - Task PutObjectAsync(ObjectHeader header, byte[] payload); + Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default); - Task PutSingleObjectAsync(ModelsV2.Object obj); + Task PutSingleObjectAsync(ModelsV2.Object obj, CancellationToken cancellationToken = default); Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs index ef21202..15a7b1c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs @@ -88,8 +88,8 @@ public static class ObjectHeaderMapper if (split.Children != null && split.Children.Any()) head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); - } - + } + return head; } @@ -103,15 +103,31 @@ public static class ObjectHeaderMapper _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") }; - return new ObjectHeader( + var model = new ObjectHeader( new ContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), objTypeName, header.Attributes.Select(attribute => attribute.ToModel()).ToArray() ) { PayloadLength = header.PayloadLength, - Version = header.Version.ToModel() + Version = header.Version.ToModel(), + OwnerId = header.OwnerId.ToModel() }; + + if (header.Split != null) + { + model.Split = new Split(SplitId.CrateFromBinary(header.Split.SplitId.ToByteArray())) + { + Parent = header.Split.Parent?.ToModel(), + ParentHeader = header.Split.ParentHeader?.ToModel(), + Previous = header.Split.Previous?.ToModel() + }; + + if (header.Split.Children.Any()) + model.Split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); + } + + return model; } } @@ -148,4 +164,3 @@ public static class SignatureMapper }; } } - diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs index 4bea66d..b4f332e 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs @@ -13,4 +13,9 @@ public static class OwnerIdMapper Value = ByteString.CopyFrom(ownerId.ToHash()) }; } + + public static OwnerId ToModel(this OwnerID ownerId) + { + return new OwnerId(ownerId.ToString()); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Services/Object.cs b/src/FrostFS.SDK.ClientV2/Services/Object.cs index bc98dc8..004df1d 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Object.cs @@ -13,6 +13,8 @@ using FrostFS.SDK.Cryptography; using FrostFS.Session; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ClientV2.Extensions; +using System.Threading; namespace FrostFS.SDK.ClientV2; @@ -32,6 +34,7 @@ public partial class Client } }; + request.AddMetaHeader(); request.Sign(_key); var response = await _objectServiceClient!.HeadAsync(request); @@ -70,18 +73,83 @@ public partial class Client return obj.ToModel(); } - public async Task PutObjectAsync(ObjectHeader header, Stream payload) + public async Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default) { - return await PutObject(header, payload); + return await PutObject(header, payload, cancellationToken); } - public async Task PutObjectAsync(ObjectHeader header, byte[] payload) + public async Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default) { using var stream = new MemoryStream(payload); - return await PutObject(header, stream); + return await PutObject(header, stream, cancellationToken); } - private async Task PutObject(ObjectHeader header, Stream payload) + private Task PutObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) + { + if (header.ClientCut) + return PutClientCutObject(header, payload, cancellationToken); + else + return PutStreamObject(header, payload, cancellationToken); + } + + private async Task PutClientCutObject(ObjectHeader header, Stream payloadStream, CancellationToken cancellationToken) + { + ObjectId? objectId = null; + List sentObjectIds = []; + ModelsV2.Object? currentObject; + + var partSize = (int)NetworkSettings["MaxObjectSize"]; + var buffer = new byte[partSize]; + + var largeObject = new LargeObject(header.ContainerId); + + var split = new Split(); + + var fullLength = (ulong)payloadStream.Length; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + + var bytesCount = await payloadStream.ReadAsync(buffer, 0, partSize); + + split.Previous = sentObjectIds.LastOrDefault(); + + largeObject.AppendBlock(buffer, bytesCount); + + currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) + .AddAttributes(header.Attributes) + .SetSplit(split); + + if (largeObject.PayloadLength == fullLength) + break; + + objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + + sentObjectIds.Add(objectId!); + } + + if (sentObjectIds.Any()) + { + largeObject.CalculateHash(); + + currentObject.SetParent(largeObject); + + objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + sentObjectIds.Add(objectId); + + var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) + .AddChildren(sentObjectIds); + + _ = await PutSingleObjectAsync(linkObject, cancellationToken); + + return currentObject.GetParentId(); + } + + return await PutSingleObjectAsync(currentObject, cancellationToken); + } + + private async Task PutStreamObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) { var sessionToken = await CreateSessionAsync(uint.MaxValue); var hdr = header.ToGrpcMessage(); @@ -120,6 +188,8 @@ public partial class Client while (true) { + cancellationToken.ThrowIfCancellationRequested(); + var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); if (bufferLength == 0) @@ -141,7 +211,7 @@ public partial class Client return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - public async Task PutSingleObjectAsync(ModelsV2.Object @object) + public async Task PutSingleObjectAsync(ModelsV2.Object @object, CancellationToken cancellationToken = default) { var sessionToken = await CreateSessionAsync(uint.MaxValue); @@ -163,7 +233,7 @@ public partial class Client request.Sign(_key); - var response = await _objectServiceClient!.PutSingleAsync(request); + var response = await _objectServiceClient!.PutSingleAsync(request, null, null, cancellationToken); Verifier.CheckResponse(response); return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); @@ -207,6 +277,8 @@ public partial class Client split.Parent = grpcHeader.Split.Parent.ToModel(); } + + grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); } var obj = new Object.Object @@ -286,10 +358,12 @@ public partial class Client request.Body.Filters.Add(filter.ToGrpcMessage()); } + request.AddMetaHeader(); request.Sign(_key); var objectsIds = SearchObjects(request); + await foreach (var oid in objectsIds) { yield return ObjectId.FromHash(oid.Value.ToByteArray()); @@ -301,12 +375,14 @@ public partial class Client using var stream = GetObjectInit(request); var obj = await stream.ReadHeader(); var payload = new byte[obj.Header.PayloadLength]; - var offset = 0; + var offset = 0L; var chunk = await stream.ReadChunk(); - while (chunk is not null) + while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) { - chunk.CopyTo(payload, offset); + var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); + + Array.Copy(chunk, 0, payload, offset, length); offset += chunk.Length; chunk = await stream.ReadChunk(); } @@ -321,6 +397,7 @@ public partial class Client if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); + return new ObjectReader { Call = _objectServiceClient!.Get(initRequest) @@ -376,3 +453,5 @@ public partial class Client } + + diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 0534720..aa2afc9 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -7,8 +7,8 @@ + - diff --git a/src/FrostFS.SDK.Cryptography/Helper.cs b/src/FrostFS.SDK.Cryptography/Helper.cs index 266a193..198dcb5 100644 --- a/src/FrostFS.SDK.Cryptography/Helper.cs +++ b/src/FrostFS.SDK.Cryptography/Helper.cs @@ -11,6 +11,7 @@ public static class Helper internal static byte[] RIPEMD160(this byte[] value) { var hash = new byte[20]; + var digest = new RipeMD160Digest(); digest.BlockUpdate(value, 0, value.Length); digest.DoFinal(hash, 0); diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs index 7e86912..d67d987 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs @@ -5,5 +5,5 @@ namespace FrostFS.SDK.ModelsV2.Netmap; public class NodeInfo { public NodeState State { get; set; } - public Version Version { get; set; } + public Version? Version { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs index 5810699..b4236b1 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -22,7 +22,7 @@ public class Object public void SetParent(LargeObject largeObject) { - Header.Split!.ParentHeader = largeObject.Header; + Header.Split.ParentHeader = largeObject.Header; } public ObjectId? GetParentId() @@ -41,10 +41,11 @@ public class LargeObject(ContainerId container) : Object(container, []) this.payloadHash.TransformBlock(bytes, 0, count, bytes, 0); } - public void CalculateHash() + public LargeObject CalculateHash() { this.payloadHash.TransformFinalBlock([], 0, 0); Header.PayloadCheckSum = this.payloadHash.Hash; + return this; } public ulong PayloadLength diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index 8f51160..db9b867 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -6,7 +6,7 @@ namespace FrostFS.SDK.ModelsV2; public class ObjectHeader { - public OwnerId OwnerId { get; set; } + public OwnerId? OwnerId { get; set; } public List Attributes { get; set; } @@ -18,7 +18,7 @@ public class ObjectHeader public ObjectType ObjectType { get; set; } - public Version Version { get; set; } + public Version? Version { get; set; } public Split? Split { get; set; } diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index 6334c71..a7cd446 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using FrostFS.Session; using Google.Protobuf; @@ -201,26 +202,31 @@ namespace FrostFS.Object public partial class DeleteResponse : IResponse { + [DebuggerStepThrough] IMetaHeader IVerificableMessage.GetMetaHeader() { return MetaHeader; } + [DebuggerStepThrough] IVerificationHeader IVerificableMessage.GetVerificationHeader() { return VerifyHeader; } + [DebuggerStepThrough] void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } + [DebuggerStepThrough] void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } + [DebuggerStepThrough] public IMessage GetBody() { return Body; diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj new file mode 100644 index 0000000..931a072 --- /dev/null +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + From c988ff3c767d37f5653d17b2e798d830ce503eb0 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 26 Jun 2024 12:29:33 +0300 Subject: [PATCH 03/65] [#11] Add Network Snapshot Signed-off-by: Pavel Gross --- .gitignore | 1 + FrostFS.SDK.sln | 11 +- src/FrostFS.SDK.ClientV2/Client.cs | 292 +++++++---- .../FrostFS.SDK.ClientV2.csproj | 7 +- .../Interfaces/IFrostFSClient.cs | 37 +- .../Mappers/{GRPC => }/Container.cs | 14 +- .../Mappers/{GRPC => }/ContainerId.cs | 0 .../Mappers/GRPC/Netmap/NodeInfo.cs | 24 - .../Mappers/{GRPC => }/MetaHeader.cs | 0 .../Mappers/Netmap/Netmap.cs | 15 + .../Mappers/Netmap/NodeInfo.cs | 35 ++ .../{GRPC => }/Netmap/PlacementPolicy.cs | 0 .../Mappers/{GRPC => }/Netmap/Replica.cs | 0 .../Mappers/Object/Object.cs | 14 + .../Mappers/Object/ObjectAttributeMapper.cs | 21 + .../Mappers/Object/ObjectFilterMapper.cs | 30 ++ .../ObjectHeaderMapper.cs} | 78 +-- .../Mappers/{GRPC => Object}/ObjectId.cs | 0 .../Mappers/{GRPC => }/OwnerId.cs | 0 .../Mappers/Session/Session.cs | 25 + .../Mappers/SignatureMapper.cs | 26 + .../Mappers/{GRPC => }/Status.cs | 0 .../Mappers/{GRPC => }/Version.cs | 0 .../Services/Container.cs | 85 --- .../Services/ContainerServiceProvider.cs | 113 ++++ src/FrostFS.SDK.ClientV2/Services/Netmap.cs | 38 -- .../Services/NetmapServiceProvider.cs | 132 +++++ .../Services/ObjectReader.cs | 10 +- .../{Object.cs => ObjectServiceProvider.cs} | 489 +++++++++--------- .../Services/SearchReader.cs | 10 +- .../{Session.cs => SessionServiceProvider.cs} | 28 +- .../Tools/ClientEnvironment.cs | 40 ++ .../Tools/ContextAccessor.cs | 8 + .../Tools/NetworkSettings.cs | 21 + .../{Extensions => Tools}/Object.cs | 14 +- src/FrostFS.SDK.ClientV2/{ => Tools}/Range.cs | 32 +- .../{ => Tools}/RequestConstructor.cs | 7 +- .../{ => Tools}/RequestSigner.cs | 9 +- .../{ => Tools}/Verifier.cs | 16 +- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 2 +- src/FrostFS.SDK.Cryptography/Base58.cs | 2 + .../{Helper.cs => Extentions.cs} | 2 +- .../FrostFS.SDK.Cryptography.csproj | 4 + src/FrostFS.SDK.Cryptography/Key.cs | 6 +- src/FrostFS.SDK.Cryptography/Range.cs | 32 +- src/FrostFS.SDK.ModelsV2/ClientSettings.cs | 28 + src/FrostFS.SDK.ModelsV2/Container.cs | 17 +- src/FrostFS.SDK.ModelsV2/Context.cs | 13 + src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs | 5 +- .../FrostFS.SDK.ModelsV2.csproj | 4 + src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs | 8 + src/FrostFS.SDK.ModelsV2/MetaHeader.cs | 15 +- src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs | 10 + src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs | 23 +- .../Netmap/PlacementPolicy.cs | 27 +- .../Object/IObjectReader.cs | 9 + src/FrostFS.SDK.ModelsV2/Object/Object.cs | 36 +- .../Object/ObjectHeader.cs | 2 - .../{ => Object}/ObjectId.cs | 0 src/FrostFS.SDK.ModelsV2/OwnerId.cs | 9 +- .../PutObjectParameters.cs | 12 + src/FrostFS.SDK.ModelsV2/SessionToken.cs | 8 + src/FrostFS.SDK.ModelsV2/Signature.cs | 8 + src/FrostFS.SDK.ModelsV2/SignatureScheme.cs | 7 + src/FrostFS.SDK.ModelsV2/SplitId.cs | 50 ++ src/FrostFS.SDK.ModelsV2/Splitter.cs | 72 +-- src/FrostFS.SDK.ModelsV2/Status.cs | 17 +- .../FrostFS.SDK.ProtosV2.csproj | 4 + .../Interfaces/IRequest.cs | 2 +- .../Interfaces/IResponse.cs | 2 +- .../Interfaces/IVerifiableMessage.cs | 2 +- .../container/Extension.Message.cs | 112 ++-- .../netmap/Extension.Message.cs | 89 +++- .../object/Extension.Message.cs | 128 ++--- .../session/Extension.Message.cs | 16 +- src/FrostFS.SDK.Tests/ClientTest.cs | 87 ++++ src/FrostFS.SDK.Tests/ClientTestLive.cs | 229 ++++++++ .../ContainerServiceBase.cs | 28 + .../DeleteContainerMock.cs | 69 +++ .../ContainerServiceMocks/GetContainerMock.cs | 163 ++++++ .../ContainerServiceMocks/PutContainerMock.cs | 88 ++++ src/FrostFS.SDK.Tests/NetmapMock.cs | 14 + src/FrostFS.SDK.Tests/ObjectMock.cs | 14 + src/FrostFS.SDK.Tests/SessionMock.cs | 14 + 84 files changed, 2238 insertions(+), 933 deletions(-) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Container.cs (75%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/ContainerId.cs (100%) delete mode 100644 src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/MetaHeader.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Netmap/PlacementPolicy.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Netmap/Replica.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC/Object.cs => Object/ObjectHeaderMapper.cs} (53%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => Object}/ObjectId.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/OwnerId.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs create mode 100644 src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Status.cs (100%) rename src/FrostFS.SDK.ClientV2/Mappers/{GRPC => }/Version.cs (100%) delete mode 100644 src/FrostFS.SDK.ClientV2/Services/Container.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs delete mode 100644 src/FrostFS.SDK.ClientV2/Services/Netmap.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs rename src/FrostFS.SDK.ClientV2/Services/{Object.cs => ObjectServiceProvider.cs} (58%) rename src/FrostFS.SDK.ClientV2/Services/{Session.cs => SessionServiceProvider.cs} (50%) create mode 100644 src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs create mode 100644 src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs rename src/FrostFS.SDK.ClientV2/{Extensions => Tools}/Object.cs (76%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/Range.cs (91%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/RequestConstructor.cs (90%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/RequestSigner.cs (95%) rename src/FrostFS.SDK.ClientV2/{ => Tools}/Verifier.cs (86%) rename src/FrostFS.SDK.Cryptography/{Helper.cs => Extentions.cs} (97%) create mode 100644 src/FrostFS.SDK.ModelsV2/ClientSettings.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Context.cs create mode 100644 src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs rename src/FrostFS.SDK.ModelsV2/{ => Object}/ObjectId.cs (100%) create mode 100644 src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SessionToken.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Signature.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SignatureScheme.cs create mode 100644 src/FrostFS.SDK.ModelsV2/SplitId.cs create mode 100644 src/FrostFS.SDK.Tests/ClientTest.cs create mode 100644 src/FrostFS.SDK.Tests/ClientTestLive.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/NetmapMock.cs create mode 100644 src/FrostFS.SDK.Tests/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/SessionMock.cs diff --git a/.gitignore b/.gitignore index cf6e05a..ae0b81f 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ vendor/ # IDE .idea .vscode +.vs # coverage coverage.txt diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index fcfe732..c192375 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" @@ -10,6 +10,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ModelsV2", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,8 +34,9 @@ Global {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 96fb227..2b87c46 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,93 +1,223 @@ -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; -using FrostFS.Container; +using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.Session; using Grpc.Core; using Grpc.Net.Client; -using static FrostFS.Netmap.NetworkConfig.Types; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; using Version = FrostFS.SDK.ModelsV2.Version; namespace FrostFS.SDK.ClientV2; -public partial class Client: IFrostFSClient +public class Client : IFrostFSClient { - private GrpcChannel? _channel; - private readonly ECDsa _key; - public readonly OwnerId OwnerId; - public readonly Version Version = new(2, 13); + private bool isDisposed; - private readonly Dictionary NetworkSettings = []; + internal ClientEnvironment ClientCtx { get; set; } - private ContainerService.ContainerServiceClient? _containerServiceClient; - private NetmapService.NetmapServiceClient? _netmapServiceClient; - private ObjectService.ObjectServiceClient? _objectServiceClient; - private SessionService.SessionServiceClient? _sessionServiceClient; - - public static IFrostFSClient GetInstance(string key, string host) + public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions channelOptions) { - return new Client(key, host); + return new Client(clientOptions, channelOptions); } - private Client(string key, string host) + /// + /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. + /// + /// Global setting for client + /// Setting for gRPC cjannel + /// ContainerService.ContainerServiceClient implementation + /// Netmap.NetmapService.NetmapServiceClient implementation + /// Session.SessionService.SessionServiceClient implementation + /// Object.ObjectService.ObjectServiceClient implementation + /// + public static IFrostFSClient GetTestInstance( + IOptions clientOptions, + GrpcChannelOptions? channelOptions, + NetmapService.NetmapServiceClient netmapService, + SessionService.SessionServiceClient sessionService, + ContainerService.ContainerServiceClient containerService, + ObjectService.ObjectServiceClient objectService) { - // TODO: Развязать клиент и реализацию GRPC - _key = key.LoadWif(); - OwnerId = OwnerId.FromKey(_key); - InitGrpcChannel(host); - InitContainerClient(); - InitNetmapClient(); - InitObjectClient(); - InitSessionClient(); + return new Client(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService); + } + + private Client( + IOptions settings, + GrpcChannelOptions? channelOptions, + ContainerService.ContainerServiceClient containerService, + NetmapService.NetmapServiceClient netmapService, + SessionService.SessionServiceClient sessionService, + ObjectService.ObjectServiceClient objectService) + { + var ecdsaKey = settings.Value.Key.LoadWif(); + OwnerId.FromKey(ecdsaKey); + + ClientCtx = new ClientEnvironment( + key: ecdsaKey, + owner: OwnerId.FromKey(ecdsaKey), + channel: InitGrpcChannel(settings.Value.Host, channelOptions), + version: new Version(2, 13)); + + ClientCtx.ContainerService = new ContainerServiceProvider(containerService, ClientCtx); + ClientCtx.NetmapService = new NetmapServiceProvider(netmapService, ClientCtx); + ClientCtx.SessionService = new SessionServiceProvider(sessionService, ClientCtx); + ClientCtx.ObjectService = new ObjectServiceProvider(objectService, ClientCtx); + } + + private Client(IOptions options, GrpcChannelOptions channelOptions) + { + var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); + + clientSettings.Validate(); + + var ecdsaKey = clientSettings.Key.LoadWif(); + + var channel = InitGrpcChannel(clientSettings.Host, channelOptions); + + ClientCtx = new ClientEnvironment( + key: ecdsaKey, + owner: OwnerId.FromKey(ecdsaKey), + channel: channel, + version: new Version(2, 13)); + + ClientCtx.ContainerService = new ContainerServiceProvider(new ContainerService.ContainerServiceClient(channel), ClientCtx); + ClientCtx.NetmapService = new NetmapServiceProvider(new NetmapService.NetmapServiceClient(channel), ClientCtx); + ClientCtx.SessionService = new SessionServiceProvider(new SessionService.SessionServiceClient(channel), ClientCtx); + ClientCtx.ObjectService = new ObjectServiceProvider(new ObjectService.ObjectServiceClient(channel), ClientCtx); + CheckFrostFsVersionSupport(); - - InitNetworkInfoAsync(); } - private async void InitNetworkInfoAsync() + public void Dispose() { - var info = await GetNetworkInfoAsync(); + Dispose(true); + GC.SuppressFinalize(this); + } - foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + protected virtual void Dispose(bool disposing) + { + if (disposing && !isDisposed) { - SetNetworksParam(param); + ClientCtx.Dispose(); + isDisposed = true; } } - private void SetNetworksParam(Parameter param) - { - var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); + public GrpcChannel Channel => ClientCtx.Channel; - var encodedValue = param.Value.ToByteArray(); - - ulong val = 0; - for (var i = encodedValue.Length - 1; i >= 0; i--) - { - val = (val << 8) + encodedValue[i]; - } - - NetworkSettings.Add(key, val); - } - - private async void CheckFrostFsVersionSupport() + public Task GetContainerAsync(ContainerId containerId, Context? ctx = null) { - var localNodeInfo = await GetLocalNodeInfoAsync(); - if (!localNodeInfo.Version.IsSupported(Version)) + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!); + } + + public IAsyncEnumerable ListContainersAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.ListContainersAsync(ctx!); + } + + public Task CreateContainerAsync(ModelsV2.Container container, Context? ctx = null) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.CreateContainerAsync(container, ctx!); + } + + public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ContainerService!.DeleteContainerAsync(containerId, ctx!); + } + + public Task GetNetmapSnapshotAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!); + } + + public Task GetNodeInfoAsync(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + } + + public Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!); + } + + public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!); + } + + public Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!); + } + + public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.PutSingleObjectAsync(obj, ctx!); + } + + public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.DeleteObjectAsync(containerId, objectId, ctx!); + } + + public IAsyncEnumerable SearchObjectsAsync( + ContainerId containerId, + IEnumerable filters, + Context? ctx = default) + { + ValidateEnvironment(ref ctx); + return ClientCtx.ObjectService!.SearchObjectsAsync(containerId, filters, ctx!); + } + + public ObjectId CalculateObjectId(ObjectHeader header) + { + return ClientCtx.ObjectService!.CalculateObjectId(header); + } + + private async void CheckFrostFsVersionSupport(Context? ctx = default) + { + ValidateEnvironment(ref ctx); + var localNodeInfo = await ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + + if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; Console.WriteLine(msg); throw new ApplicationException(msg); } } - - private void InitGrpcChannel(string host) + + private void ValidateEnvironment(ref Context? ctx) + { + if (isDisposed) + throw new Exception("Client is disposed."); + + if (ClientCtx == null || !ClientCtx.Initialized) + throw new Exception("Client is not initialized."); + + ctx ??= new Context(); + } + + private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) { Uri uri; try @@ -97,49 +227,25 @@ public partial class Client: IFrostFSClient catch (UriFormatException e) { var msg = $"Host '{host}' has invalid format. Error: {e.Message}"; - Console.WriteLine(msg); throw new ArgumentException(msg); } - ChannelCredentials grpcCredentials; - switch (uri.Scheme) + ChannelCredentials grpcCredentials = uri.Scheme switch { - case "https": - grpcCredentials = ChannelCredentials.SecureSsl; - break; - case "http": - grpcCredentials = ChannelCredentials.Insecure; - break; - default: - var msg = $"Host '{host}' has invalid URI scheme: '{uri.Scheme}'."; - Console.WriteLine(msg); - throw new ArgumentException(msg); + "https" => ChannelCredentials.SecureSsl, + "http" => ChannelCredentials.Insecure, + _ => throw new ArgumentException($"Host '{host}' has invalid URI scheme: '{uri.Scheme}'.") + }; + + if (channelOptions != null) + { + return GrpcChannel.ForAddress(uri, channelOptions); } - - _channel = GrpcChannel.ForAddress(uri, new GrpcChannelOptions - { - Credentials = grpcCredentials, - HttpHandler = new System.Net.Http.HttpClientHandler() - }); - } - - private void InitContainerClient() - { - _containerServiceClient = new ContainerService.ContainerServiceClient(_channel); - } - - private void InitNetmapClient() - { - _netmapServiceClient = new NetmapService.NetmapServiceClient(_channel); - } - - private void InitObjectClient() - { - _objectServiceClient = new ObjectService.ObjectServiceClient(_channel); - } - - private void InitSessionClient() - { - _sessionServiceClient = new SessionService.SessionServiceClient(_channel); + + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions + { + Credentials = grpcCredentials, + HttpHandler = new HttpClientHandler() + }); } } diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index d446e0e..818bce2 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -6,9 +6,14 @@ enable + + true + + - + + diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 9ae6833..4234a1d 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -1,34 +1,41 @@ +using System; using System.Collections.Generic; -using System.IO; -using System.Threading; using System.Threading.Tasks; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; +using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2.Interfaces; -public interface IFrostFSClient +public interface IFrostFSClient : IDisposable { - Task GetContainerAsync(ContainerId containerId); + Task GetContainerAsync(ContainerId containerId, Context? context = default); - IAsyncEnumerable ListContainersAsync(); + IAsyncEnumerable ListContainersAsync(Context? context = default); - Task CreateContainerAsync(ModelsV2.Container container); + Task CreateContainerAsync(ModelsV2.Container container, Context? context = default); - Task DeleteContainerAsync(ContainerId containerId); + Task DeleteContainerAsync(ContainerId containerId, Context? context = default); - Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId); + Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task GetObjectAsync(ContainerId containerId, ObjectId objectId); + Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default); + + Task PutSingleObjectAsync(ModelsV2.Object obj, Context? context = default); - Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default); + Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default); + IAsyncEnumerable SearchObjectsAsync(ContainerId cid, IEnumerable filters, Context? context = default); - Task PutSingleObjectAsync(ModelsV2.Object obj, CancellationToken cancellationToken = default); + Task GetNetmapSnapshotAsync(Context? context = default); - Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId); + Task GetNodeInfoAsync(Context? context = default); + + ObjectId CalculateObjectId(ObjectHeader header); + + GrpcChannel Channel { get; } +} - IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters); -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs similarity index 75% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Container.cs index 93eac3f..3d1207f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs @@ -22,19 +22,15 @@ public static class ContainerMapper public static ModelsV2.Container ToModel(this Container.Container container) { - var basicAclName = Enum.GetName(typeof(BasicAcl), container.BasicAcl); - if (basicAclName is null) - { + if (!Enum.IsDefined(typeof(BasicAcl),(int)container.BasicAcl)) throw new ArgumentException($"Unknown BasicACL rule. Value: '{container.BasicAcl}'."); - } + + BasicAcl acl = (BasicAcl)container.BasicAcl; - return new ModelsV2.Container( - (BasicAcl)Enum.Parse(typeof(BasicAcl), basicAclName), - container.PlacementPolicy.ToModel() - ) + return new ModelsV2.Container(acl, container.PlacementPolicy.ToModel()) { Nonce = container.Nonce.ToUuid(), Version = container.Version.ToModel() }; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/ContainerId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs deleted file mode 100644 index f8f2d56..0000000 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/NodeInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -using FrostFS.Netmap; -using FrostFS.SDK.ModelsV2.Enums; -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; - -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; - -public static class NodeInfoMapper -{ - public static NodeInfo ToModel(this LocalNodeInfoResponse.Types.Body nodeInfo) - { - var nodeStateName = Enum.GetName(typeof(NodeState), nodeInfo.NodeInfo.State); - if (nodeStateName is null) - { - throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.NodeInfo.State}'."); - } - return new NodeInfo - { - State = (NodeState)Enum.Parse(typeof(NodeState), nodeStateName), - Version = nodeInfo.Version.ToModel() - }; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/MetaHeader.cs rename to src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs new file mode 100644 index 0000000..359041c --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs @@ -0,0 +1,15 @@ +using System.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.ModelsV2.Netmap; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; + +public static class NetmapMapper +{ + public static NetmapSnapshot ToModel(this NetmapSnapshotResponse netmap) + { + return new NetmapSnapshot( + netmap.Body.Netmap.Epoch, + netmap.Body.Netmap.Nodes.Select(n => n.ToModel(netmap.MetaHeader.Version)).ToArray()); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs new file mode 100644 index 0000000..da87dfd --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.ModelsV2.Enums; +using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; + +public static class NodeInfoMapper +{ + public static NodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node) + { + return node.NodeInfo.ToModel(node.Version); + } + + public static NodeInfo ToModel(this FrostFS.Netmap.NodeInfo nodeInfo, Refs.Version version) + { + NodeState state = nodeInfo.State switch + { + FrostFS.Netmap.NodeInfo.Types.State.Unspecified => NodeState.Unspecified, + FrostFS.Netmap.NodeInfo.Types.State.Online => NodeState.Online, + FrostFS.Netmap.NodeInfo.Types.State.Offline => NodeState.Offline, + FrostFS.Netmap.NodeInfo.Types.State.Maintenance => NodeState.Maintenance, + _ => throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.State}'.") + }; + + return new NodeInfo( + version: version.ToModel(), + state: state, + addresses: [.. nodeInfo.Addresses], + attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value), + publicKey: nodeInfo.PublicKey.ToByteArray() + ); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/PlacementPolicy.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Netmap/Replica.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs new file mode 100644 index 0000000..be61b13 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -0,0 +1,14 @@ +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectMapper +{ + public static ModelsV2.Object ToModel(this Object.Object obj) + { + return new ModelsV2.Object( + ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), + obj.Header.ToModel(), + obj.Payload.ToByteArray()); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs new file mode 100644 index 0000000..75c4bb8 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs @@ -0,0 +1,21 @@ +using FrostFS.Object; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectAttributeMapper +{ + public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) + { + return new Header.Types.Attribute + { + Key = attribute.Key, + Value = attribute.Value + }; + } + + public static ObjectAttribute ToModel(this Header.Types.Attribute attribute) + { + return new ObjectAttribute(attribute.Key, attribute.Value); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs new file mode 100644 index 0000000..ee819c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -0,0 +1,30 @@ +using System; +using FrostFS.Object; +using FrostFS.SDK.ModelsV2; +using MatchType = FrostFS.Object.MatchType; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class ObjectFilterMapper +{ + public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) + { + var objMatchTypeName = filter.MatchType switch + { + ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, + ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, + ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, + ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, + ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, + + _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") + }; + + return new SearchRequest.Types.Body.Types.Filter + { + MatchType = objMatchTypeName, + Key = filter.Key, + Value = filter.Value + }; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs similarity index 53% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 15a7b1c..6ded9d2 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -4,52 +4,10 @@ using FrostFS.Object; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; -using MatchType = FrostFS.Object.MatchType; using ObjectType = FrostFS.Object.ObjectType; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; -public static class ObjectAttributeMapper -{ - public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) - { - return new Header.Types.Attribute - { - Key = attribute.Key, - Value = attribute.Value - }; - } - - public static ObjectAttribute ToModel(this Header.Types.Attribute attribute) - { - return new ObjectAttribute(attribute.Key, attribute.Value); - } -} - -public static class ObjectFilterMapper -{ - public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this ObjectFilter filter) - { - var objMatchTypeName = filter.MatchType switch - { - ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, - ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, - ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, - ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, - ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, - - _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") - }; - - return new SearchRequest.Types.Body.Types.Filter - { - MatchType = objMatchTypeName, - Key = filter.Key, - Value = filter.Value - }; - } -} - public static class ObjectHeaderMapper { public static Header ToGrpcMessage(this ObjectHeader header) @@ -116,7 +74,7 @@ public static class ObjectHeaderMapper if (header.Split != null) { - model.Split = new Split(SplitId.CrateFromBinary(header.Split.SplitId.ToByteArray())) + model.Split = new Split(SplitId.CreateFromBinary(header.Split.SplitId.ToByteArray())) { Parent = header.Split.Parent?.ToModel(), ParentHeader = header.Split.ParentHeader?.ToModel(), @@ -130,37 +88,3 @@ public static class ObjectHeaderMapper return model; } } - -public static class ObjectMapper -{ - public static ModelsV2.Object ToModel(this Object.Object obj) - { - return new ModelsV2.Object() - { - Header = obj.Header.ToModel(), - ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), - Payload = obj.Payload.ToByteArray() - }; - } -} - -public static class SignatureMapper -{ - public static Refs.Signature ToGrpcMessage(this Signature signature) - { - var scheme = signature.Scheme switch - { - SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, - SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, - SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, - _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) - }; - - return new Refs.Signature - { - Key = ByteString.CopyFrom(signature.Key), - Scheme = scheme, - Sign = ByteString.CopyFrom(signature.Sign) - }; - } -} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/ObjectId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/OwnerId.cs rename to src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs new file mode 100644 index 0000000..402f7e1 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -0,0 +1,25 @@ + +using System; +using Google.Protobuf; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class SessionMapper +{ + internal static string SerializeSessionToken(this Session.SessionToken token) + { + byte[] bytes = new byte[token.CalculateSize()]; + CodedOutputStream stream = new(bytes); + token.WriteTo(stream); + + return Convert.ToBase64String(bytes); + } + + internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes) + { + Session.SessionToken token = new(); + token.MergeFrom(bytes); + + return token; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs new file mode 100644 index 0000000..2da358a --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs @@ -0,0 +1,26 @@ +using System; +using FrostFS.SDK.ModelsV2; +using Google.Protobuf; + +namespace FrostFS.SDK.ClientV2.Mappers.GRPC; + +public static class SignatureMapper +{ + public static Refs.Signature ToGrpcMessage(this Signature signature) + { + var scheme = signature.Scheme switch + { + SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, + SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, + SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, + _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) + }; + + return new Refs.Signature + { + Key = ByteString.CopyFrom(signature.Key), + Scheme = scheme, + Sign = ByteString.CopyFrom(signature.Sign) + }; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Status.cs b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Status.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Status.cs diff --git a/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Version.cs b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Mappers/GRPC/Version.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Version.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/Container.cs b/src/FrostFS.SDK.ClientV2/Services/Container.cs deleted file mode 100644 index 7566492..0000000 --- a/src/FrostFS.SDK.ClientV2/Services/Container.cs +++ /dev/null @@ -1,85 +0,0 @@ -using FrostFS.Container; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace FrostFS.SDK.ClientV2; - -public partial class Client -{ - public async Task GetContainerAsync(ContainerId cid) - { - var request = new GetRequest - { - Body = new GetRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage() - }, - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.GetAsync(request); - Verifier.CheckResponse(response); - return response.Body.Container.ToModel(); - } - - public async IAsyncEnumerable ListContainersAsync() - { - var request = new ListRequest - { - Body = new ListRequest.Types.Body - { - OwnerId = OwnerId.ToGrpcMessage() - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.ListAsync(request); - Verifier.CheckResponse(response); - foreach (var cid in response.Body.ContainerIds) - { - yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); - } - } - - public async Task CreateContainerAsync(ModelsV2.Container container) - { - var cntnr = container.ToGrpcMessage(); - cntnr.OwnerId = OwnerId.ToGrpcMessage(); - cntnr.Version = Version.ToGrpcMessage(); - - var request = new PutRequest - { - Body = new PutRequest.Types.Body - { - Container = cntnr, - Signature = _key.SignRFC6979(cntnr), - } - }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.PutAsync(request); - Verifier.CheckResponse(response); - return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); - } - - public async Task DeleteContainerAsync(ContainerId cid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Signature = _key.SignRFC6979(cid.ToGrpcMessage().Value) - } - }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _containerServiceClient.DeleteAsync(request); - Verifier.CheckResponse(response); - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs new file mode 100644 index 0000000..b2d72ee --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -0,0 +1,113 @@ +using System.Threading.Tasks; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ModelsV2; +using FrostFS.Container; + +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using System.Collections.Generic; + +namespace FrostFS.SDK.ClientV2; + +internal class ContainerServiceProvider : ContextAccessor +{ + private readonly ContainerService.ContainerServiceClient containerServiceClient; + + internal ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) + : base(context) + { + containerServiceClient = service; + } + + internal async Task GetContainerAsync(ContainerId cid, Context context) + { + var request = new GetRequest + { + Body = new GetRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage() + }, + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await containerServiceClient.GetAsync(request, null, context.Deadline, context.CancellationToken); + + Verifier.CheckResponse(response); + + return response.Body.Container.ToModel(); + } + + internal async IAsyncEnumerable ListContainersAsync(Context ctx) + { + var request = new ListRequest + { + Body = new ListRequest.Types.Body + { + OwnerId = Context.Owner.ToGrpcMessage() + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + foreach (var cid in response.Body.ContainerIds) + { + yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); + } + } + + internal async Task CreateContainerAsync(ModelsV2.Container container, Context ctx) + { + var grpcContainer = container.ToGrpcMessage(); + grpcContainer.OwnerId = Context.Owner.ToGrpcMessage(); + grpcContainer.Version = Context.Version.ToGrpcMessage(); + + var request = new PutRequest + { + Body = new PutRequest.Types.Body + { + Container = grpcContainer, + Signature = Context.Key.SignRFC6979(grpcContainer), + } + }; + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + //var response = await Context.InvokeAsyncUnaryWithMetrics(() => + // containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken), + // nameof(containerServiceClient.PutAsync)); + + Verifier.CheckResponse(response); + + return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); + } + + internal async Task DeleteContainerAsync(ContainerId cid, Context ctx) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Signature = Context.Key.SignRFC6979(cid.ToGrpcMessage().Value) + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + } +} + + diff --git a/src/FrostFS.SDK.ClientV2/Services/Netmap.cs b/src/FrostFS.SDK.ClientV2/Services/Netmap.cs deleted file mode 100644 index ce439cc..0000000 --- a/src/FrostFS.SDK.ClientV2/Services/Netmap.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Threading.Tasks; - -using FrostFS.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; - -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; - -namespace FrostFS.SDK.ClientV2; - -public partial class Client -{ - public async Task GetLocalNodeInfoAsync() - { - var request = new LocalNodeInfoRequest - { - Body = new LocalNodeInfoRequest.Types.Body { } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _netmapServiceClient.LocalNodeInfoAsync(request); - - return response.Body.ToModel(); - } - - public async Task GetNetworkInfoAsync() - { - var request = new NetworkInfoRequest - { - Body = new NetworkInfoRequest.Types.Body { } - }; - - request.AddMetaHeader(); - request.Sign(_key); - - return await _netmapServiceClient.NetworkInfoAsync(request); - } -} diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs new file mode 100644 index 0000000..1060a20 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -0,0 +1,132 @@ +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; + +using FrostFS.SDK.ModelsV2.Netmap; +using static FrostFS.Netmap.NetworkConfig.Types; + +namespace FrostFS.SDK.ClientV2; + +internal class NetmapServiceProvider : ContextAccessor +{ + private readonly NetmapService.NetmapServiceClient netmapServiceClient; + + internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context) + : base(context) + { + this.netmapServiceClient = netmapServiceClient; + } + + internal async Task GetNetworkSettingsAsync(Context ctx) + { + if (Context.NetworkSettings != null) + return Context.NetworkSettings; + + var info = await GetNetworkInfoAsync(ctx); + + var settings = new NetworkSettings(); + + foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + { + SetNetworksParam(param, settings); + } + + Context.NetworkSettings = settings; + + return settings; + } + + internal async Task GetLocalNodeInfoAsync(Context ctx) + { + var request = new LocalNodeInfoRequest + { + Body = new LocalNodeInfoRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + //var response = await Context.InvokeAsyncUnaryWithMetrics(() => + // netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken), + // nameof(netmapServiceClient.LocalNodeInfoAsync)); + + Verifier.CheckResponse(response); + + return response.Body.ToModel(); + } + + internal async Task GetNetworkInfoAsync(Context ctx) + { + var request = new NetworkInfoRequest + { + Body = new NetworkInfoRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return response; + } + + internal async Task GetNetmapSnapshotAsync(Context ctx) + { + var request = new NetmapSnapshotRequest + { + Body = new NetmapSnapshotRequest.Types.Body { } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return response.ToModel(); + } + + private static bool GetBoolValue(byte[] bytes) + { + return bytes.Any(b => b != 0); + } + + private static ulong GetLongValue(byte[] bytes) + { + ulong val = 0; + for (var i = bytes.Length - 1; i >= 0; i--) + val = (val << 8) + bytes[i]; + + return val; + } + + private static void SetNetworksParam(Parameter param, NetworkSettings settings) + { + var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); + + var valueBytes = param.Value.ToByteArray(); + switch (key) + { + case "ContainerFee": settings.ContainerFee = GetLongValue(valueBytes); break; + case "EpochDuration": settings.EpochDuration = GetLongValue(valueBytes); break; + case "IRCandidateFee": settings.IRCandidateFee = GetLongValue(valueBytes); break; + case "MaxECDataCount": settings.MaxECDataCount = GetLongValue(valueBytes); break; + case "MaxECParityCount": settings.MaxECParityCount = GetLongValue(valueBytes); break; + case "MaxObjectSize": settings.MaxObjectSize = GetLongValue(valueBytes); break; + case "WithdrawalFee": settings.WithdrawalFee = GetLongValue(valueBytes); break; + case "HomomorphicHashingDisabled": settings.HomomorphicHashingDisabled = GetBoolValue(valueBytes); break; + case "MaintenanceModeAllowed": settings.MaintenanceModeAllowed = GetBoolValue(valueBytes); break; + default: settings.UnnamedSettings.Add(key, valueBytes); break; + } + } +} + + diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs index 415c001..73e982a 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs @@ -7,20 +7,20 @@ using FrostFS.Object; namespace FrostFS.SDK.ClientV2; -internal class ObjectReader : IDisposable +internal class ObjectReader(AsyncServerStreamingCall call) : IDisposable { - public AsyncServerStreamingCall Call { get; set; } + public AsyncServerStreamingCall Call { get; private set; } = call; public async Task ReadHeader() { if (!await Call.ResponseStream.MoveNext()) - throw new InvalidOperationException("unexpect end of stream"); + throw new InvalidOperationException("unexpected end of stream"); var response = Call.ResponseStream.Current; Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init) - throw new InvalidOperationException("unexpect message type"); + throw new InvalidOperationException("unexpected message type"); return new Object.Object { @@ -38,7 +38,7 @@ internal class ObjectReader : IDisposable Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk) - throw new InvalidOperationException("unexpect message type"); + throw new InvalidOperationException("unexpected message type"); return response.Body.Chunk.ToByteArray(); } diff --git a/src/FrostFS.SDK.ClientV2/Services/Object.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs similarity index 58% rename from src/FrostFS.SDK.ClientV2/Services/Object.cs rename to src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 004df1d..8619cdd 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,26 +1,33 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading.Tasks; + using Google.Protobuf; +using System.Threading.Tasks; + using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; - using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ClientV2.Extensions; -using System.Threading; namespace FrostFS.SDK.ClientV2; -public partial class Client +internal class ObjectServiceProvider : ContextAccessor { - public async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid) + private readonly ObjectService.ObjectServiceClient objectServiceClient; + + internal ObjectServiceProvider(ObjectService.ObjectServiceClient objectServiceClient, ClientEnvironment context) + : base (context) + { + this.objectServiceClient = objectServiceClient; + } + + internal async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx) { var request = new HeadRequest { @@ -34,18 +41,20 @@ public partial class Client } }; - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient!.HeadAsync(request); + request.Sign(Context.Key); + + var response = await objectServiceClient!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); + Verifier.CheckResponse(response); return response.Body.Header.Header.ToModel(); } - public async Task GetObjectAsync(ContainerId cid, ObjectId oid) + internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); + var sessionToken = await GetOrCreateSession(ctx); + var request = new GetRequest { Body = new GetRequest.Types.Body @@ -64,41 +73,131 @@ public partial class Client cid.ToGrpcMessage(), oid.ToGrpcMessage(), ObjectSessionContext.Types.Verb.Get, - _key + Context.Key ); - request.Sign(_key); - var obj = await GetObject(request); + request.Sign(Context.Key); + + var obj = await GetObject(request, ctx); return obj.ToModel(); } - public async Task PutObjectAsync(ObjectHeader header, Stream payload, CancellationToken cancellationToken = default) + internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) { - return await PutObject(header, payload, cancellationToken); - } - - public async Task PutObjectAsync(ObjectHeader header, byte[] payload, CancellationToken cancellationToken = default) - { - using var stream = new MemoryStream(payload); - return await PutObject(header, stream, cancellationToken); - } - - private Task PutObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) - { - if (header.ClientCut) - return PutClientCutObject(header, payload, cancellationToken); + if (parameters.Header == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Header)); + + if (parameters.Payload == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Payload)); + + if (parameters.ClientCut) + return PutClientCutObject(parameters, ctx); else - return PutStreamObject(header, payload, cancellationToken); + return PutStreamObject(parameters, ctx); + } + + internal async Task PutSingleObjectAsync(ModelsV2.Object @object, Context ctx) + { + var sessionToken = await GetOrCreateSession(ctx); + + var obj = CreateObject(@object); + + var request = new PutSingleRequest + { + Body = new PutSingleRequest.Types.Body() + { + Object = obj + } + }; + + request.AddMetaHeader(); + request.AddObjectSessionToken( + sessionToken, + obj.Header.ContainerId, + obj.ObjectId, + ObjectSessionContext.Types.Verb.Put, + Context.Key + ); + + request.Sign(Context.Key); + + var response = await objectServiceClient!.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); } - private async Task PutClientCutObject(ObjectHeader header, Stream payloadStream, CancellationToken cancellationToken) + internal ObjectId CalculateObjectId(ObjectHeader header) { - ObjectId? objectId = null; + var grpcHeader = CreateHeader(header, []); + + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + } + + internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + { + var request = new DeleteRequest + { + Body = new DeleteRequest.Types.Body + { + Address = new Address + { + ContainerId = cid.ToGrpcMessage(), + ObjectId = oid.ToGrpcMessage() + } + } + }; + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var response = await objectServiceClient!.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + } + + internal async IAsyncEnumerable SearchObjectsAsync( + ContainerId cid, + IEnumerable filters, + Context ctx) + { + var request = new SearchRequest + { + Body = new SearchRequest.Types.Body + { + ContainerId = cid.ToGrpcMessage(), + Filters = { }, + Version = 1 // TODO: clarify this param + } + }; + + request.Body.Filters.AddRange(filters.Select(f => f.ToGrpcMessage())); + + request.AddMetaHeader(); + request.Sign(Context.Key); + + var objectsIds = SearchObjects(request, ctx); + + await foreach (var oid in objectsIds) + { + yield return ObjectId.FromHash(oid.Value.ToByteArray()); + } + } + + private async Task PutClientCutObject(PutObjectParameters parameters, Context ctx) + { + var payloadStream = parameters.Payload!; + var header = parameters.Header!; + + ObjectId? objectId; List sentObjectIds = []; ModelsV2.Object? currentObject; - var partSize = (int)NetworkSettings["MaxObjectSize"]; + var networkSettings = await Context.NetmapService!.GetNetworkSettingsAsync(ctx); + + var partSize = (int)networkSettings.MaxObjectSize; var buffer = new byte[partSize]; var largeObject = new LargeObject(header.ContainerId); @@ -109,8 +208,6 @@ public partial class Client while (true) { - cancellationToken.ThrowIfCancellationRequested(); - var bytesCount = await payloadStream.ReadAsync(buffer, 0, partSize); split.Previous = sentObjectIds.LastOrDefault(); @@ -124,37 +221,41 @@ public partial class Client if (largeObject.PayloadLength == fullLength) break; - objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + objectId = await PutSingleObjectAsync(currentObject, ctx); sentObjectIds.Add(objectId!); } - if (sentObjectIds.Any()) + if (sentObjectIds.Count != 0) { largeObject.CalculateHash(); currentObject.SetParent(largeObject); - objectId = await PutSingleObjectAsync(currentObject, cancellationToken); + objectId = await PutSingleObjectAsync(currentObject, ctx); sentObjectIds.Add(objectId); var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) .AddChildren(sentObjectIds); - _ = await PutSingleObjectAsync(linkObject, cancellationToken); + _ = await PutSingleObjectAsync(linkObject, ctx); - return currentObject.GetParentId(); + return CalculateObjectId(largeObject.Header); } - return await PutSingleObjectAsync(currentObject, cancellationToken); + return await PutSingleObjectAsync(currentObject, ctx); } - private async Task PutStreamObject(ObjectHeader header, Stream payload, CancellationToken cancellationToken) + private async Task PutStreamObject(PutObjectParameters parameters, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); + var payload = parameters.Payload!; + var header = parameters.Header!; + + var sessionToken = await GetOrCreateSession(ctx); + var hdr = header.ToGrpcMessage(); - hdr.OwnerId = OwnerId.ToGrpcMessage(); - hdr.Version = Version.ToGrpcMessage(); + hdr.OwnerId = Context.Owner.ToGrpcMessage(); + hdr.Version = Context.Version.ToGrpcMessage(); var oid = new ObjectID { @@ -178,19 +279,17 @@ public partial class Client hdr.ContainerId, oid, ObjectSessionContext.Types.Verb.Put, - _key + Context.Key ); - request.Sign(_key); + request.Sign(Context.Key); - using var stream = await PutObjectInit(request); + using var stream = await PutObjectInit(request, ctx); var buffer = new byte[Constants.ObjectChunkSize]; while (true) { - cancellationToken.ThrowIfCancellationRequested(); - - var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize); + var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize, ctx.CancellationToken); if (bufferLength == 0) break; @@ -201,7 +300,7 @@ public partial class Client }; request.VerifyHeader = null; - request.Sign(_key); + request.Sign(Context.Key); await stream.Write(request); } @@ -211,168 +310,11 @@ public partial class Client return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - public async Task PutSingleObjectAsync(ModelsV2.Object @object, CancellationToken cancellationToken = default) + // TODO: add implementation with stream writer! + private async Task GetObject(GetRequest request, Context ctx) { - var sessionToken = await CreateSessionAsync(uint.MaxValue); - - var obj = CreateObject(@object); - - var request = new PutSingleRequest - { - Body = new () { Object = obj } - }; - - request.AddMetaHeader(); - request.AddObjectSessionToken( - sessionToken, - obj.Header.ContainerId, - obj.ObjectId, - ObjectSessionContext.Types.Verb.Put, - _key - ); - - request.Sign(_key); - - var response = await _objectServiceClient!.PutSingleAsync(request, null, null, cancellationToken); - Verifier.CheckResponse(response); - - return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); - } - - public Object.Object CreateObject(ModelsV2.Object @object) - { - var grpcHeader = @object.Header.ToGrpcMessage(); - - grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); - grpcHeader.Version = Version.ToGrpcMessage(); - - if (@object.Payload != null) - { - grpcHeader.PayloadLength = (ulong)@object.Payload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); - } - - var split = @object.Header.Split; - if (split != null) - { - grpcHeader.Split = new Header.Types.Split - { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null - }; - - if (split.Children != null && split.Children.Any()) - grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); - - if (split.ParentHeader is not null) - { - var grpcParentHeader = CreateHeader(split.ParentHeader, []); - - grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; - grpcHeader.Split.ParentHeader = grpcParentHeader; - grpcHeader.Split.ParentSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(_key.PublicKey()), - Sign = ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), - }; - - split.Parent = grpcHeader.Split.Parent.ToModel(); - } - - grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); - } - - var obj = new Object.Object - { - Header = grpcHeader, - ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, - Payload = ByteString.CopyFrom(@object.Payload) - }; - - obj.Signature = new Refs.Signature - { - Key = ByteString.CopyFrom(_key.PublicKey()), - Sign = ByteString.CopyFrom(_key.SignData(obj.ObjectId.ToByteArray())), - }; + using var stream = GetObjectInit(request, ctx); - return obj; - } - - public Header CreateHeader(ObjectHeader header, byte[]? payload) - { - var grpcHeader = header.ToGrpcMessage(); - - grpcHeader.OwnerId = OwnerId.ToGrpcMessage(); - grpcHeader.Version = Version.ToGrpcMessage(); - - if (header.PayloadCheckSum != null) - { - grpcHeader.PayloadHash = new Checksum - { - Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(header.PayloadCheckSum) - }; - } - else - { - if (payload != null) - grpcHeader.PayloadHash = Sha256Checksum(payload); - } - - return grpcHeader; - } - - public async Task DeleteObjectAsync(ContainerId cid, ObjectId oid) - { - var request = new DeleteRequest - { - Body = new DeleteRequest.Types.Body - { - Address = new Address - { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() - } - } - }; - - request.AddMetaHeader(); - request.Sign(_key); - var response = await _objectServiceClient!.DeleteAsync(request); - Verifier.CheckResponse(response); - } - - public async IAsyncEnumerable SearchObjectsAsync(ContainerId cid, params ObjectFilter[] filters) - { - var request = new SearchRequest - { - Body = new SearchRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage(), - Filters = { }, - Version = 1 - } - }; - - foreach (var filter in filters) - { - request.Body.Filters.Add(filter.ToGrpcMessage()); - } - - - request.AddMetaHeader(); - request.Sign(_key); - var objectsIds = SearchObjects(request); - - - await foreach (var oid in objectsIds) - { - yield return ObjectId.FromHash(oid.Value.ToByteArray()); - } - } - - private async Task GetObject(GetRequest request) - { - using var stream = GetObjectInit(request); var obj = await stream.ReadHeader(); var payload = new byte[obj.Header.PayloadLength]; var offset = 0L; @@ -392,57 +334,134 @@ public partial class Client return obj; } - private ObjectReader GetObjectInit(GetRequest initRequest) + private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) { if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); - - return new ObjectReader - { - Call = _objectServiceClient!.Get(initRequest) - }; + var call = objectServiceClient!.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); + + return new ObjectReader(call); } - private async Task PutObjectInit(PutRequest initRequest) + private async Task PutObjectInit(PutRequest initRequest, Context ctx) { if (initRequest is null) { throw new ArgumentNullException(nameof(initRequest)); } - var call = _objectServiceClient!.Put(); + var call = objectServiceClient!.Put(null, ctx.Deadline, ctx.CancellationToken); + await call.RequestStream.WriteAsync(initRequest); return new ObjectStreamer(call); } - private async IAsyncEnumerable SearchObjects(SearchRequest request) + private async IAsyncEnumerable SearchObjects(SearchRequest request, Context ctx) { - using var stream = GetSearchReader(request); - var ids = await stream.Read(); - while (ids is not null) + using var stream = GetSearchReader(request, ctx); + + while (true) { + var ids = await stream.Read(ctx.CancellationToken); + + if (ids == null) + break; + foreach (var oid in ids) { yield return oid; } - - ids = await stream.Read(); } } - private SearchReader GetSearchReader(SearchRequest initRequest) + private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx) { if (initRequest is null) { throw new ArgumentNullException(nameof(initRequest)); } - return new SearchReader(_objectServiceClient!.Search(initRequest)); + var call = objectServiceClient!.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); + + return new SearchReader(call); + } + + private Object.Object CreateObject(ModelsV2.Object @object) + { + var grpcHeader = @object.Header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (@object.Payload != null) + { + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + } + + var split = @object.Header.Split; + if (split != null) + { + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Count != 0) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, []); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + + grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); + } + + var obj = new Object.Object + { + Header = grpcHeader, + ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + Payload = ByteString.CopyFrom(@object.Payload) + }; + + obj.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), + }; + + return obj; } - public Checksum Sha256Checksum(byte[] data) + private Header CreateHeader(ObjectHeader header, byte[]? payload) + { + var grpcHeader = header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (header.PayloadCheckSum != null) + grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); + else if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + + return grpcHeader; + } + + private static Checksum Sha256Checksum(byte[] data) { return new Checksum { @@ -450,8 +469,14 @@ public partial class Client Sum = ByteString.CopyFrom(data.Sha256()) }; } + + private async Task GetOrCreateSession(Context ctx) + { + if (string.IsNullOrEmpty(ctx.SessionToken)) + { + return await Context.SessionService!.CreateSessionAsync(uint.MaxValue, ctx); + } + + return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken(); + } } - - - - diff --git a/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs b/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs index c3d425a..6bc72df 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs @@ -7,6 +7,7 @@ using Grpc.Core; using FrostFS.Object; using FrostFS.Refs; +using System.Threading; namespace FrostFS.SDK.ClientV2; @@ -14,14 +15,13 @@ internal class SearchReader(AsyncServerStreamingCall call) : IDi { public AsyncServerStreamingCall Call { get; private set; } = call; - public async Task?> Read() + public async Task?> Read(CancellationToken cancellationToken) { - if (!await Call.ResponseStream.MoveNext()) - { + if (!await Call.ResponseStream.MoveNext(cancellationToken)) return null; - } - + var response = Call.ResponseStream.Current; + Verifier.CheckResponse(response); return response.Body?.IdList.ToList(); diff --git a/src/FrostFS.SDK.ClientV2/Services/Session.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs similarity index 50% rename from src/FrostFS.SDK.ClientV2/Services/Session.cs rename to src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 3dc52a1..c8c3c47 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -5,28 +5,38 @@ using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -public partial class Client -{ - private async Task CreateSessionAsync(ulong expiration) +internal class SessionServiceProvider : ContextAccessor +{ + private readonly SessionService.SessionServiceClient? _sessionServiceClient; + + internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context) + : base (context) + { + _sessionServiceClient = sessionServiceClient; + } + + internal async Task CreateSessionAsync(ulong expiration, Context ctx) { var request = new CreateRequest { Body = new CreateRequest.Types.Body { - OwnerId = OwnerId.ToGrpcMessage(), - Expiration = expiration, + OwnerId = Context.Owner.ToGrpcMessage(), + Expiration = expiration } }; request.AddMetaHeader(); - request.Sign(_key); + request.Sign(Context.Key); - return await CreateSession(request); + var token = await CreateSession(request, ctx); + + return token; } - private async Task CreateSession(CreateRequest request) + internal async Task CreateSession(CreateRequest request, Context ctx) { - var resp = await _sessionServiceClient.CreateAsync(request); + var resp = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); return new SessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs new file mode 100644 index 0000000..037078b --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -0,0 +1,40 @@ +using FrostFS.SDK.ModelsV2; +using Grpc.Net.Client; +using System; +using System.Security.Cryptography; + +namespace FrostFS.SDK.ClientV2; + +public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +{ + internal OwnerId Owner { get; } = owner; + internal GrpcChannel Channel { get; private set; } = channel; + internal ECDsa Key { get; } = key; + internal ModelsV2.Version Version { get; } = version; + internal NetworkSettings? NetworkSettings { get; set; } + + internal ContainerServiceProvider? ContainerService { get; set; } + internal NetmapServiceProvider? NetmapService { get; set; } + internal SessionServiceProvider? SessionService { get; set; } + internal ObjectServiceProvider? ObjectService { get; set; } + + internal bool Initialized => + ContainerService != null + && NetmapService != null + && SessionService != null + && ObjectService != null; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Channel.Dispose(); + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs new file mode 100644 index 0000000..75edcc6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ClientV2; + +internal class ContextAccessor(ClientEnvironment context) +{ + protected ClientEnvironment Context { get; set; } = context; +} + + diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs new file mode 100644 index 0000000..03067c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK.ClientV2; + +public class NetworkSettings + { + public ulong ContainerFee { get; internal set; } + public ulong ContainerAliasFee { get; internal set; } + public ulong InnerRingCandidateFee { get; internal set; } + public ulong WithdrawFee { get; internal set; } + public ulong EpochDuration { get; internal set; } + public ulong IRCandidateFee { get; internal set; } + public ulong MaxObjectSize { get; internal set; } + public ulong MaxECDataCount { get; internal set; } + public ulong MaxECParityCount { get; internal set; } + public ulong WithdrawalFee { get; internal set; } + public bool HomomorphicHashingDisabled { get; internal set; } + public bool MaintenanceModeAllowed { get; internal set; } + + public Dictionary UnnamedSettings { get; } = []; + } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs similarity index 76% rename from src/FrostFS.SDK.ClientV2/Extensions/Object.cs rename to src/FrostFS.SDK.ClientV2/Tools/Object.cs index 3765e71..e7e59f1 100644 --- a/src/FrostFS.SDK.ClientV2/Extensions/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -1,10 +1,11 @@ +using System; using System.Collections.Generic; using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Extensions; -public static class Extensions +public static class ObjectExtensions { public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) { @@ -41,4 +42,15 @@ public static class Extensions linkObject.Header.Split!.Children.AddRange(objectIds); return linkObject; } + + public static ModelsV2.Object CalculateObjectId(this ModelsV2.Object obj) + { + if (obj.Payload == null) + throw new MissingFieldException("Payload cannot be null"); + + if (obj.Header == null) + throw new MissingFieldException("Header cannot be null"); + + return obj; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Range.cs b/src/FrostFS.SDK.ClientV2/Tools/Range.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Range.cs rename to src/FrostFS.SDK.ClientV2/Tools/Range.cs index 8158314..9c989c1 100644 --- a/src/FrostFS.SDK.ClientV2/Range.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Range.cs @@ -41,10 +41,10 @@ namespace System } /// Create an Index pointing at first element. - public static Index Start => new Index(0); + public static Index Start => new(0); /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); + public static Index End => new(~0); /// Create an Index from the start at the position indicated by the value. /// The index value from the start. @@ -116,7 +116,7 @@ namespace System /// Indicates whether the current Index object is equal to another object of the same type. /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + public override bool Equals(object? value) => value is Index index && _value == index._value; /// Indicates whether the current Index object is equal to another Index object. /// An object to compare with this object @@ -147,22 +147,16 @@ namespace System /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } /// /// - internal readonly struct Range : IEquatable + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + internal readonly struct Range(Index start, Index end) : IEquatable { /// Represent the inclusive start index of the Range. - public Index Start { get; } + public Index Start { get; } = start; /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + public Index End { get; } = end; /// Indicates whether the current Range object is equal to another object of the same type. /// An object to compare with this object @@ -188,13 +182,13 @@ namespace System } /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); + public static Range StartAt(Index start) => new(start, Index.End); /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + public static Range EndAt(Index end) => new(Index.Start, end); /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + public static Range All => new(Index.Start, Index.End); /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. @@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices if (length == 0) { - return Array.Empty(); + return []; } var dest = new T[length]; diff --git a/src/FrostFS.SDK.ClientV2/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs similarity index 90% rename from src/FrostFS.SDK.ClientV2/RequestConstructor.cs rename to src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index fb632ee..6b1d4cf 100644 --- a/src/FrostFS.SDK.ClientV2/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -17,13 +17,15 @@ public static class RequestConstructor public static void AddObjectSessionToken( this IRequest request, - SessionToken sessionToken, + Session.SessionToken sessionToken, ContainerID cid, ObjectID oid, ObjectSessionContext.Types.Verb verb, ECDsa key) { - if (request.MetaHeader.SessionToken is not null) return; + if (request.MetaHeader.SessionToken is not null) + return; + request.MetaHeader.SessionToken = sessionToken; var ctx = new ObjectSessionContext { @@ -34,6 +36,7 @@ public static class RequestConstructor }, Verb = verb }; + request.MetaHeader.SessionToken.Body.Object = ctx; request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body); } diff --git a/src/FrostFS.SDK.ClientV2/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/RequestSigner.cs rename to src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index 23f8eb6..6016ddf 100644 --- a/src/FrostFS.SDK.ClientV2/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -84,7 +84,7 @@ public static class RequestSigner return sig; } - public static void Sign(this IVerificableMessage message, ECDsa key) + public static void Sign(this IVerifiableMessage message, ECDsa key) { var meta = message.GetMetaHeader(); IVerificationHeader verify = message switch @@ -95,12 +95,15 @@ public static class RequestSigner }; var verifyOrigin = message.GetVerificationHeader(); + if (verifyOrigin is null) verify.BodySignature = key.SignMessagePart(message.GetBody()); - + else + verify.SetOrigin(verifyOrigin); + verify.MetaSignature = key.SignMessagePart(meta); verify.OriginSignature = key.SignMessagePart(verifyOrigin); - verify.SetOrigin(verifyOrigin); + message.SetVerificationHeader(verify); } } diff --git a/src/FrostFS.SDK.ClientV2/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs similarity index 86% rename from src/FrostFS.SDK.ClientV2/Verifier.cs rename to src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index ad6b3e6..5811ff1 100644 --- a/src/FrostFS.SDK.ClientV2/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -68,7 +68,7 @@ public static class Verifier return false; using var key = sig.Key.ToByteArray().LoadPublicKey(); - var data2Verify = data is null ? Array.Empty() : data.ToByteArray(); + var data2Verify = data is null ? [] : data.ToByteArray(); return key.VerifyData(data2Verify, sig.Sign.ToByteArray()); } @@ -89,7 +89,7 @@ public static class Verifier return verification.BodySignature is null && VerifyMatryoskaLevel(body, meta.GetOrigin(), origin); } - public static bool Verify(this IVerificableMessage message) + public static bool Verify(this IVerifiableMessage message) { return VerifyMatryoskaLevel(message.GetBody(), message.GetMetaHeader(), message.GetVerificationHeader()); } @@ -100,7 +100,17 @@ public static class Verifier throw new FormatException($"invalid response, type={resp.GetType()}"); var status = resp.MetaHeader.Status.ToModel(); - if (!status.IsSuccess()) + if (!status.IsSuccess) throw new ApplicationException(status.ToString()); } + + /// + /// This method is intended for unit tests for request verification. + /// + /// Created by SDK request to gRpc proxy + public static void CheckRequest(IRequest request) + { + if (!request.Verify()) + throw new FormatException($"invalid response, type={request.GetType()}"); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index a593c3a..a75808b 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -17,4 +17,4 @@ using System.Runtime.InteropServices; [assembly: Guid("08a8487e-39ce-41fb-9c24-13f73ff2bde0")] -[assembly: InternalsVisibleToAttribute("FrostFS.SDK.Cryptography.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("FrostFS.SDK.Cryptography.Test")] \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index d9d91cf..f33794a 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -34,9 +34,11 @@ public static class Base58 byte[] checksum = data.ToArray().Sha256().Sha256(); Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); + checksum[..4].AsSpan().CopyTo(buffer[data.Length..]); var ret = Encode(buffer); buffer.Clear(); + return ret; } diff --git a/src/FrostFS.SDK.Cryptography/Helper.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs similarity index 97% rename from src/FrostFS.SDK.Cryptography/Helper.cs rename to src/FrostFS.SDK.Cryptography/Extentions.cs index 198dcb5..c27f5ef 100644 --- a/src/FrostFS.SDK.Cryptography/Helper.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -6,7 +6,7 @@ using System.Security.Cryptography; namespace FrostFS.SDK.Cryptography; -public static class Helper +public static class Extentions { internal static byte[] RIPEMD160(this byte[] value) { diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index aa2afc9..e276848 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index 56f71cb..de96791 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -56,7 +56,7 @@ public static class KeyExtension var script = new byte[] { 0x0c, CompressedPublicKeyLength }; //PUSHDATA1 33 script = ArrayHelper.Concat(script, publicKey); - script = ArrayHelper.Concat(script, new byte[] { 0x41 }); //SYSCALL + script = ArrayHelper.Concat(script, [0x41]); //SYSCALL script = ArrayHelper.Concat(script, BitConverter.GetBytes(CheckSigDescriptor)); //Neo_Crypto_CheckSig return script; @@ -80,7 +80,7 @@ public static class KeyExtension private static byte[] GetPrivateKeyFromWIF(string wif) { if (wif == null) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(wif)); var data = wif.Base58CheckDecode(); @@ -117,7 +117,7 @@ public static class KeyExtension var pos = 33 - param.Q.X.Length; param.Q.X.CopyTo(pubkey, pos); - if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x00 }).ToArray()).IsEven) + if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x0 }).ToArray()).IsEven) pubkey[0] = 0x2; else pubkey[0] = 0x3; diff --git a/src/FrostFS.SDK.Cryptography/Range.cs b/src/FrostFS.SDK.Cryptography/Range.cs index 8158314..7313a64 100644 --- a/src/FrostFS.SDK.Cryptography/Range.cs +++ b/src/FrostFS.SDK.Cryptography/Range.cs @@ -41,10 +41,10 @@ namespace System } /// Create an Index pointing at first element. - public static Index Start => new Index(0); + public static Index Start => new(0); /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); + public static Index End => new(~0); /// Create an Index from the start at the position indicated by the value. /// The index value from the start. @@ -116,7 +116,7 @@ namespace System /// Indicates whether the current Index object is equal to another object of the same type. /// An object to compare with this object - public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + public override bool Equals(object? value) => value is Index index && _value == index._value; /// Indicates whether the current Index object is equal to another Index object. /// An object to compare with this object @@ -147,22 +147,16 @@ namespace System /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } /// /// - internal readonly struct Range : IEquatable + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + internal readonly struct Range (Index start, Index end) : IEquatable { /// Represent the inclusive start index of the Range. - public Index Start { get; } + public Index Start { get; } = start; /// Represent the exclusive end index of the Range. - public Index End { get; } - - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + public Index End { get; } = end; /// Indicates whether the current Range object is equal to another object of the same type. /// An object to compare with this object @@ -188,13 +182,13 @@ namespace System } /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); + public static Range StartAt(Index start) => new(start, Index.End); /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + public static Range EndAt(Index end) => new(Index.Start, end); /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + public static Range All => new(Index.Start, Index.End); /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. @@ -252,7 +246,7 @@ namespace System.Runtime.CompilerServices if (length == 0) { - return Array.Empty(); + return []; } var dest = new T[length]; diff --git a/src/FrostFS.SDK.ModelsV2/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/ClientSettings.cs new file mode 100644 index 0000000..4268d53 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/ClientSettings.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; + +namespace FrostFS.SDK.ModelsV2; + +public class ClientSettings +{ + private static readonly string errorTemplate = "{0} is required parameter"; + + public string Key { get; set; } = string.Empty; + + public string Host { get; set; } = string.Empty; + + public void Validate() + { + StringBuilder? error = null; + + if (string.IsNullOrWhiteSpace(Key)) + (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Key))); + + if (string.IsNullOrWhiteSpace(Host)) + (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Host))); + + if (error != null) + throw new ArgumentException(error.ToString()); + } + +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Container.cs b/src/FrostFS.SDK.ModelsV2/Container.cs index d32ff8f..4533325 100644 --- a/src/FrostFS.SDK.ModelsV2/Container.cs +++ b/src/FrostFS.SDK.ModelsV2/Container.cs @@ -5,17 +5,10 @@ using FrostFS.SDK.ModelsV2.Netmap; namespace FrostFS.SDK.ModelsV2; -public class Container +public class Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) { - public Guid Nonce { get; set; } - public BasicAcl BasicAcl { get; set; } - public PlacementPolicy PlacementPolicy { get; set; } - public Version Version { get; set; } - - public Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) - { - Nonce = Guid.NewGuid(); - BasicAcl = basicAcl; - PlacementPolicy = placementPolicy; - } + public Guid Nonce { get; set; } = Guid.NewGuid(); + public BasicAcl BasicAcl { get; set; } = basicAcl; + public PlacementPolicy PlacementPolicy { get; set; } = placementPolicy; + public Version? Version { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs new file mode 100644 index 0000000..302dafc --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading; + +namespace FrostFS.SDK.ClientV2; + +public class Context() +{ + public CancellationToken CancellationToken { get; set; } = default; + public TimeSpan Timeout { get; set; } = default; + public string SessionToken { get; set; } = string.Empty; + + public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; +} diff --git a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs index 5c222fc..9418039 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs +++ b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs @@ -3,7 +3,10 @@ using System.ComponentModel; namespace FrostFS.SDK.ModelsV2.Enums; public enum BasicAcl -{ +{ + [Description("Not defined ACL")] + NotDefined = 0x00000000, + [Description("Basic ACL for private container")] Private = 0x1C8C8CCC, diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index 96a477c..0f47676 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs b/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs new file mode 100644 index 0000000..71ac276 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class GrpcCallInfo(string methodName, long elapsedMicroSec, bool hasError) +{ + public string MethodName { get; set; } = methodName; + public long ElapsedTimeMicroSec { get; set; } = elapsedMicroSec; + public bool HasError { get; } = hasError; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/MetaHeader.cs b/src/FrostFS.SDK.ModelsV2/MetaHeader.cs index 68abbb3..d2b84a2 100644 --- a/src/FrostFS.SDK.ModelsV2/MetaHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/MetaHeader.cs @@ -1,17 +1,10 @@ namespace FrostFS.SDK.ModelsV2; -public class MetaHeader +public class MetaHeader(Version version, int epoch, int ttl) { - public Version Version { get; set; } - public int Epoch { get; set; } - public int Ttl { get; set; } - - public MetaHeader(Version version, int epoch, int ttl) - { - Version = version; - Epoch = epoch; - Ttl = ttl; - } + public Version Version { get; set; } = version; + public int Epoch { get; set; } = epoch; + public int Ttl { get; set; } = ttl; public static MetaHeader Default() { diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs new file mode 100644 index 0000000..fb8dba1 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK.ModelsV2.Netmap; + +public class NetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) +{ + public ulong Epoch { get; private set; } = epoch; + + public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs index d67d987..9433c04 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs @@ -1,9 +1,28 @@ +using System; +using System.Collections.Generic; using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2.Netmap; public class NodeInfo { - public NodeState State { get; set; } - public Version? Version { get; set; } + public NodeInfo( + Version version, + NodeState state, + IReadOnlyCollection addresses, + IReadOnlyDictionary attributes, + ReadOnlyMemory publicKey) + { + Version = version; + State = state; + Addresses = addresses; + Attributes = attributes; + PublicKey = publicKey; + } + + public NodeState State { get; private set; } + public Version Version { get; private set; } + public IReadOnlyCollection Addresses { get; private set; } + public IReadOnlyDictionary Attributes { get; private set; } + public ReadOnlyMemory PublicKey { get; private set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs index b638bb6..bb7b1a3 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs @@ -1,13 +1,28 @@ +using System; +using System.Linq; + namespace FrostFS.SDK.ModelsV2.Netmap; -public class PlacementPolicy +public class PlacementPolicy(bool unique, params Replica[] replicas) : IComparable { - public Replica[] Replicas { get; private set; } - public bool Unique { get; private set; } + public Replica[] Replicas { get; private set; } = replicas; + public bool Unique { get; private set; } = unique; - public PlacementPolicy(bool unique, params Replica[] replicas) + public int CompareTo(PlacementPolicy other) { - Replicas = replicas; - Unique = unique; + var notEqual = other == null + || Unique != other.Unique + || Replicas.Length != other.Replicas.Length; + + if (notEqual) + return 1; + + foreach (var replica in Replicas) + { + if (!other!.Replicas.Any(r => r.Count == replica.Count && r.Selector == replica.Selector)) + return 1; + } + + return 0; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs new file mode 100644 index 0000000..3822dce --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs @@ -0,0 +1,9 @@ +using System; +using System.Threading.Tasks; + +namespace FrostFS.SDK.ModelsV2; + +public interface IObjectReader : IDisposable +{ + Task ReadChunk(); +} diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs index b4236b1..51c4630 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -1,3 +1,4 @@ +using System; using System.Security.Cryptography; using FrostFS.SDK.ModelsV2.Enums; @@ -5,8 +6,11 @@ namespace FrostFS.SDK.ModelsV2; public class Object { - public Object() + public Object(ObjectId objectId, ObjectHeader header, byte[] payload) { + ObjectId = objectId; + Payload = payload; + Header = header; } public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) @@ -14,43 +18,47 @@ public class Object Payload = payload; Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); } - + public ObjectHeader Header { get; set; } - public ObjectId ObjectId { get; set; } + + public ObjectId? ObjectId + { + get; internal set; + } + public byte[] Payload { get; set; } - public Signature Signature { get; set; } + + public Signature? Signature { get; set; } public void SetParent(LargeObject largeObject) { - Header.Split.ParentHeader = largeObject.Header; - } + if (Header?.Split == null) + throw new Exception("The object is not initialized properly"); - public ObjectId? GetParentId() - { - return Header.Split?.Parent; + Header.Split.ParentHeader = largeObject.Header; } } public class LargeObject(ContainerId container) : Object(container, []) { - private SHA256 payloadHash = SHA256.Create(); + private readonly SHA256 payloadHash = SHA256.Create(); public void AppendBlock(byte[] bytes, int count) { - Header.PayloadLength += (ulong)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; + Header!.PayloadCheckSum = this.payloadHash.Hash; return this; } public ulong PayloadLength { - get { return Header.PayloadLength; } + get { return Header!.PayloadLength; } } } @@ -58,7 +66,7 @@ public class LinkObject : Object { public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) { - Header.Split = new Split(splitId) + Header!.Split = new Split(splitId) { ParentHeader = largeObject.Header }; diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index db9b867..83bee87 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -22,8 +22,6 @@ public class ObjectHeader public Split? Split { get; set; } - public bool ClientCut { get; set; } - public ObjectHeader( ContainerId containerId, ObjectType type = ObjectType.Regular, diff --git a/src/FrostFS.SDK.ModelsV2/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/ObjectId.cs rename to src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs diff --git a/src/FrostFS.SDK.ModelsV2/OwnerId.cs b/src/FrostFS.SDK.ModelsV2/OwnerId.cs index 85cf239..8746572 100644 --- a/src/FrostFS.SDK.ModelsV2/OwnerId.cs +++ b/src/FrostFS.SDK.ModelsV2/OwnerId.cs @@ -4,14 +4,9 @@ using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.ModelsV2; -public class OwnerId +public class OwnerId(string id) { - public string Value { get; } - - public OwnerId(string id) - { - Value = id; - } + public string Value { get; } = id; public static OwnerId FromKey(ECDsa key) { diff --git a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs new file mode 100644 index 0000000..23cbc38 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs @@ -0,0 +1,12 @@ +using System.IO; + +namespace FrostFS.SDK.ModelsV2; + +public class PutObjectParameters +{ + public ObjectHeader? Header { get; set; } + + public Stream? Payload { get; set; } + + public bool ClientCut { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/SessionToken.cs new file mode 100644 index 0000000..6fde99e --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SessionToken.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class SessionToken(byte[] sessionKey, byte[] id) +{ + public byte[] Id { get; } = id; + + public byte[] SessionKey { get; } = sessionKey; +} diff --git a/src/FrostFS.SDK.ModelsV2/Signature.cs b/src/FrostFS.SDK.ModelsV2/Signature.cs new file mode 100644 index 0000000..1d36e1c --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Signature.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ModelsV2; + +public class Signature +{ + public byte[]? Key { get; set; } + public byte[]? Sign { get; set; } + public SignatureScheme Scheme { get; set; } +} diff --git a/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs b/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs new file mode 100644 index 0000000..bd6c41d --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK.ModelsV2; + +public enum SignatureScheme { + EcdsaSha512, + EcdsaRfc6979Sha256, + EcdsaRfc6979Sha256WalletConnect + } diff --git a/src/FrostFS.SDK.ModelsV2/SplitId.cs b/src/FrostFS.SDK.ModelsV2/SplitId.cs new file mode 100644 index 0000000..eddbd14 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/SplitId.cs @@ -0,0 +1,50 @@ +using System; + +namespace FrostFS.SDK.ModelsV2; + +public class SplitId +{ + private Guid id; + + public SplitId() + { + this.id = Guid.NewGuid(); + } + public SplitId(Guid guid) + { + this.id = guid; + } + + private SplitId(byte[] binary) + { + this.id = new Guid(binary); + } + + private SplitId(string str) + { + this.id = new Guid(str); + } + + public static SplitId CreateFromBinary(byte[] binaryData) + { + return new SplitId(binaryData); + } + + public static SplitId CreateFromString(string stringData) + { + return new SplitId(stringData); + } + + public override string ToString() + { + return this.id.ToString(); + } + + public byte[]? ToBinary() + { + if (this.id == Guid.Empty) + return null; + + return this.id.ToByteArray(); + } +} diff --git a/src/FrostFS.SDK.ModelsV2/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Splitter.cs index f48f903..d4622df 100644 --- a/src/FrostFS.SDK.ModelsV2/Splitter.cs +++ b/src/FrostFS.SDK.ModelsV2/Splitter.cs @@ -1,20 +1,14 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace FrostFS.SDK.ModelsV2; -public class Split +public class Split(SplitId splitId) { public Split() : this(new SplitId()) { } - public Split(SplitId splitId) - { - SplitId = splitId; - } - - public SplitId SplitId { get; private set; } + public SplitId SplitId { get; private set; } = splitId; public ObjectId? Parent { get; set; } @@ -26,63 +20,3 @@ public class Split public List Children { get; } = []; } - - public enum SignatureScheme { - EcdsaSha512, - EcdsaRfc6979Sha256, - EcdsaRfc6979Sha256WalletConnect - } - -public class Signature -{ - public byte[] Key { get; set; } - public byte[] Sign { get; set; } - public SignatureScheme Scheme { get; set; } -} - -public class SplitId -{ - private Guid id; - - public SplitId() - { - this.id = Guid.NewGuid(); - } - public SplitId(Guid guid) - { - this.id = guid; - } - - private SplitId(byte[] binary) - { - this.id = new Guid(binary); - } - - private SplitId(string str) - { - this.id = new Guid(str); - } - - public static SplitId CrateFromBinary(byte[] binaryData) - { - return new SplitId(binaryData); - } - - public static SplitId CrateFromString(string stringData) - { - return new SplitId(stringData); - } - - public override string ToString() - { - return this.id.ToString(); - } - - public byte[]? ToBinary() - { - if (this.id == Guid.Empty) - return null; - - return this.id.ToByteArray(); - } -} diff --git a/src/FrostFS.SDK.ModelsV2/Status.cs b/src/FrostFS.SDK.ModelsV2/Status.cs index 2d56441..d52d189 100644 --- a/src/FrostFS.SDK.ModelsV2/Status.cs +++ b/src/FrostFS.SDK.ModelsV2/Status.cs @@ -2,21 +2,12 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class Status +public class Status(StatusCode code, string? message = null) { - public StatusCode Code { get; set; } - public string Message { get; set; } + public StatusCode Code { get; set; } = code; + public string Message { get; set; } = message ?? string.Empty; - public Status(StatusCode code, string? message = null) - { - Code = code; - Message = message ?? string.Empty; - } - - public bool IsSuccess() - { - return Code == StatusCode.Success; - } + public bool IsSuccess => Code == StatusCode.Success; public override string ToString() { diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj index 746a8b4..0a559ed 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj @@ -6,6 +6,10 @@ enable + + true + + diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs index f75db43..7efad42 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs @@ -1,6 +1,6 @@ namespace FrostFS.Session; -public interface IRequest : IVerificableMessage +public interface IRequest : IVerifiableMessage { RequestMetaHeader MetaHeader { get; set; } RequestVerificationHeader VerifyHeader { get; set; } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs index 609be8e..ac6382a 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs @@ -1,6 +1,6 @@ namespace FrostFS.Session; -public interface IResponse : IVerificableMessage +public interface IResponse : IVerifiableMessage { ResponseMetaHeader MetaHeader { get; set; } ResponseVerificationHeader VerifyHeader { get; set; } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs index 3903757..57fce64 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs @@ -2,7 +2,7 @@ using Google.Protobuf; namespace FrostFS.Session; -public interface IVerificableMessage : IMessage +public interface IVerifiableMessage : IMessage { IMetaHeader GetMetaHeader(); void SetMetaHeader(IMetaHeader metaHeader); diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index a46ddc2..d8bd601 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -6,22 +6,22 @@ namespace FrostFS.Container; public partial class AnnounceUsedSpaceRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -34,22 +34,22 @@ public partial class AnnounceUsedSpaceRequest : IRequest public partial class AnnounceUsedSpaceResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -62,22 +62,22 @@ public partial class AnnounceUsedSpaceResponse : IResponse public partial class GetRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -90,22 +90,22 @@ public partial class GetRequest : IRequest public partial class GetResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -118,22 +118,22 @@ public partial class GetResponse : IResponse public partial class PutRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -146,22 +146,22 @@ public partial class PutRequest : IRequest public partial class PutResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -174,22 +174,22 @@ public partial class PutResponse : IResponse public partial class DeleteRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -202,22 +202,22 @@ public partial class DeleteRequest : IRequest public partial class DeleteResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -230,22 +230,22 @@ public partial class DeleteResponse : IResponse public partial class ListRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -258,22 +258,22 @@ public partial class ListRequest : IRequest public partial class ListResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -286,22 +286,22 @@ public partial class ListResponse : IResponse public partial class SetExtendedACLRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -314,22 +314,22 @@ public partial class SetExtendedACLRequest : IRequest public partial class SetExtendedACLResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -342,22 +342,22 @@ public partial class SetExtendedACLResponse : IResponse public partial class GetExtendedACLRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -370,22 +370,22 @@ public partial class GetExtendedACLRequest : IRequest public partial class GetExtendedACLResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs index 207568d..ba2d11e 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs @@ -5,22 +5,22 @@ namespace FrostFS.Netmap; public partial class LocalNodeInfoRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -33,22 +33,22 @@ public partial class LocalNodeInfoRequest : IRequest public partial class LocalNodeInfoResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -61,22 +61,22 @@ public partial class LocalNodeInfoResponse : IResponse public partial class NetworkInfoRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -89,22 +89,22 @@ public partial class NetworkInfoRequest : IRequest public partial class NetworkInfoResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -114,3 +114,60 @@ public partial class NetworkInfoResponse : IResponse return Body; } } + + +public partial class NetmapSnapshotRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class NetmapSnapshotResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index a7cd446..de933fb 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -6,22 +6,22 @@ namespace FrostFS.Object { public partial class GetRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -34,22 +34,22 @@ namespace FrostFS.Object public partial class GetResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -62,22 +62,22 @@ namespace FrostFS.Object public partial class PutRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -90,22 +90,22 @@ namespace FrostFS.Object public partial class PutResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -118,22 +118,22 @@ namespace FrostFS.Object public partial class PutSingleRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -146,22 +146,22 @@ namespace FrostFS.Object public partial class PutSingleResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -174,22 +174,22 @@ namespace FrostFS.Object public partial class DeleteRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -203,25 +203,25 @@ namespace FrostFS.Object public partial class DeleteResponse : IResponse { [DebuggerStepThrough] - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } [DebuggerStepThrough] - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } [DebuggerStepThrough] - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } [DebuggerStepThrough] - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -235,22 +235,22 @@ namespace FrostFS.Object public partial class HeadRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -263,22 +263,22 @@ namespace FrostFS.Object public partial class HeadResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -290,22 +290,22 @@ namespace FrostFS.Object } public partial class SearchRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -318,22 +318,22 @@ namespace FrostFS.Object public partial class SearchResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -346,22 +346,22 @@ namespace FrostFS.Object public partial class GetRangeRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -374,22 +374,22 @@ namespace FrostFS.Object public partial class GetRangeResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -402,22 +402,22 @@ namespace FrostFS.Object public partial class GetRangeHashRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } @@ -430,22 +430,22 @@ namespace FrostFS.Object public partial class GetRangeHashResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs index fd78adc..937af24 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs @@ -4,22 +4,22 @@ namespace FrostFS.Session; public partial class CreateResponse : IResponse { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (ResponseMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (ResponseVerificationHeader)verificationHeader; } @@ -32,22 +32,22 @@ public partial class CreateResponse : IResponse public partial class CreateRequest : IRequest { - IMetaHeader IVerificableMessage.GetMetaHeader() + IMetaHeader IVerifiableMessage.GetMetaHeader() { return MetaHeader; } - IVerificationHeader IVerificableMessage.GetVerificationHeader() + IVerificationHeader IVerifiableMessage.GetVerificationHeader() { return VerifyHeader; } - void IVerificableMessage.SetMetaHeader(IMetaHeader metaHeader) + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) { MetaHeader = (RequestMetaHeader)metaHeader; } - void IVerificableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) { VerifyHeader = (RequestVerificationHeader)verificationHeader; } diff --git a/src/FrostFS.SDK.Tests/ClientTest.cs b/src/FrostFS.SDK.Tests/ClientTest.cs new file mode 100644 index 0000000..e543b7f --- /dev/null +++ b/src/FrostFS.SDK.Tests/ClientTest.cs @@ -0,0 +1,87 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public class ClientTest +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + [Fact] + public async void CreateContainerTest() + { + var factory = new PutContainerMock(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid() + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMock(this.key).GetMock().Object, + new SessionMock(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMock(this.key).GetMock().Object); + + var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); + + Assert.NotNull(result); + Assert.NotNull(result.Value); + Assert.True(Base58.Encode(factory.ContainerGuid.ToBytes()) == result.Value); + } + + [Fact] + public async void GetContainerTest() + { + var factory = new GetContainerMock(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + Acl = BasicAcl.PublicRO, + ContainerGuid = Guid.NewGuid(), + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMock(this.key).GetMock().Object, + new SessionMock(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMock(this.key).GetMock().Object); + + var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); + + var context = new Context(); + + var result = await fsClient.GetContainerAsync(cid, context); + + Assert.NotNull(result); + Assert.Equal(factory.Acl, result.BasicAcl); + Assert.Equal(factory.ContainerGuid, result.Nonce); + Assert.Equal(0, factory.PlacementPolicy.CompareTo(result.PlacementPolicy)); + Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); + } + + // [Fact] + // public async void DeleteObjectAsyncTest() + // { + // } +} diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/ClientTestLive.cs new file mode 100644 index 0000000..70dda66 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ClientTestLive.cs @@ -0,0 +1,229 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using Grpc.Core; +using Grpc.Net.Client; +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public class ClientTestLive +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + private readonly string url = "http://172.29.238.97:8080"; + + [Fact] + public async void NetworkMapTest() + { + var channelOptions = new GrpcChannelOptions + { + Credentials = ChannelCredentials.Insecure, + HttpHandler = new HttpClientHandler() + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + + var result = await fsClient.GetNetmapSnapshotAsync(); + + Assert.True(result.Epoch > 0); + Assert.Single(result.NodeInfoCollection); + + var item = result.NodeInfoCollection[0]; + Assert.Equal(2, item.Version.Major); + Assert.Equal(13, item.Version.Minor); + Assert.Equal(NodeState.Online, item.State); + Assert.True(item.PublicKey.Length > 0); + Assert.Single(item.Addresses); + Assert.Equal(9, item.Attributes.Count); + } + + [Fact] + public async void NodeInfoTest() + { + var channelOptions = new GrpcChannelOptions + { + Credentials = ChannelCredentials.Insecure, + HttpHandler = new HttpClientHandler() + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + + var result = await fsClient.GetNodeInfoAsync(); + + Assert.Equal(2, result.Version.Major); + Assert.Equal(13, result.Version.Minor); + Assert.Equal(NodeState.Online, result.State); + Assert.True(result.PublicKey.Length > 0); + Assert.Single(result.Addresses); + Assert.Equal(9, result.Attributes.Count); + } + + [Fact] + public async void SimpleScenarioTest() + { + var channelOptions = new GrpcChannelOptions + { + Credentials = ChannelCredentials.Insecure, + HttpHandler = new HttpClientHandler() + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + + await Cleanup(fsClient); + + var containerId = await fsClient.CreateContainerAsync( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + + var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + + var container = await GetContainer(fsClient, containerId, context); + + Assert.NotNull(container); + + var param = new PutObjectParameters + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + ClientCut = false + }; + + var objectId = await fsClient.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) + { + hasObject = true; + + var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); + Assert.Equal(10u, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await fsClient.GetObjectAsync(containerId, objectId!); + + Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + + await Cleanup(fsClient); + + await Task.Delay(2000); + + await foreach (var _ in fsClient.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void ClientCutScenarioTest() + { + var channelOptions = new GrpcChannelOptions + { + Credentials = ChannelCredentials.Insecure, + HttpHandler = new HttpClientHandler() + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + + await Cleanup(fsClient); + + var containerId = await fsClient.CreateContainerAsync( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + + var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var container = await GetContainer(fsClient, containerId, context); + + Assert.NotNull(container); + + var param = new PutObjectParameters + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + ClientCut = true + }; + + var objectId = await fsClient.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) + { + hasObject = true; + + var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); + Assert.Equal(10u, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await fsClient.GetObjectAsync(containerId, objectId!); + + Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + + await Cleanup(fsClient); + + await Task.Delay(2000); + + await foreach (var _ in fsClient.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + private static IOptions GetOptions(string key, string url) + { + var settings = new ClientSettings + { + Key = key, + Host = url + }; + + return Options.Create(settings); + } + + static async Task Cleanup(IFrostFSClient fsClient) + { + await foreach (var cid in fsClient.ListContainersAsync()) + { + await fsClient.DeleteContainerAsync(cid); + } + } + + static async Task GetContainer(IFrostFSClient fsClient, ContainerId id, Context ctx) + { + while (true) + { + try + { + await Task.Delay(100); + return await fsClient.GetContainerAsync(id, ctx); + } + catch (ApplicationException) + { + if (DateTime.UtcNow >= ctx.Deadline) + throw new TimeoutException(); + } + catch (Grpc.Core.RpcException) + { + throw; + } + } + } +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs new file mode 100644 index 0000000..bbf1476 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs @@ -0,0 +1,28 @@ +using System.Security.Cryptography; +using FrostFS.Container; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; + +namespace FrostFS.SDK.Tests; + +public abstract class ServiceBase(string key) +{ + public ECDsa Key { get; private set; } = key.LoadWif(); + public ModelsV2.Version Version { get; set; } = DefaultVersion; + public BasicAcl Acl { get; set; } = DefaultAcl; + public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; + + public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); + public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; + public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); +} + +public abstract class ContainerServiceBase(string key) : ServiceBase (key) +{ + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); + + public abstract Mock GetMock(); +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs new file mode 100644 index 0000000..a752d10 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs @@ -0,0 +1,69 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +namespace FrostFS.SDK.Tests; + +public class DeleteContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())); + + + v.Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + var deleteResponse = new Object.DeleteResponse + { + Body = new Object.DeleteResponse.Types.Body + { + Tombstone = new Refs.Address + { + ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + return new AsyncUnaryCall( + Task.FromResult(deleteResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + }); + + return mock; + } +} + + + // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => + // { + // return new + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // } + // }; + // }); + + + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs new file mode 100644 index 0000000..61feb5e --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs @@ -0,0 +1,163 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public class GetContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var grpcVersion = Version.ToGrpcMessage(); + + var getResponse = new GetResponse + { + Body = new GetResponse.Types.Body + { + Container = new Container.Container + { + Version = grpcVersion, + Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), + BasicAcl = (uint)Acl, + PlacementPolicy = PlacementPolicy.ToGrpcMessage() + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = grpcVersion, + Epoch = 100, + Ttl = 1 + } + }; + + getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); + + var metadata = new Metadata(); + var getContainerResponse = new AsyncUnaryCall( + Task.FromResult(getResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.GetAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return getContainerResponse; + }); + + return mock; + } + + private ResponseVerificationHeader GetResponseVerificationHeader(GetResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } +} + +// objectServiceClientMock.Setup( +// x => x.Put(It.IsAny(), It.IsAny(), It.IsAny())) +// .Returns((Metadata m, DateTime dt, CancellationToken ct) => +// { +// return new AsyncClientStreamingCall(null, null, null, null, null, null); + +// //IClientStreamWriter requestStream, Task responseAsync, Task responseHeadersAsync, Func getStatusFunc, Func getTrailersFunc, Action disposeAction +// }); + +// return objectServiceClientMock; +// } + + +// } + + + // public virtual global::FrostFS.Object.HeadResponse Head(global::FrostFS.Object.HeadRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) + // { + // return Head(request, new grpc::CallOptions(headers, deadline, cancellationToken)); + // } + + // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => + // { + // return new + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // } + // }; + // }); + + + // objectServiceClientMock.Setup(x => x.Delete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + // { + // return new Object.DeleteResponse + // { + // Body = new Object.DeleteResponse.Types.Body + // { + // Tombstone = new Refs.Address + // { + // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, + // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } + // } + // }, + // MetaHeader = new ResponseMetaHeader() + // { + // }, + // VerifyHeader = new ResponseVerificationHeader() + // { + // MetaSignature = new Refs.Signature + // { + // Key = ByteString.CopyFrom(_key.PublicKey()), + // Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + // Sign = ByteString.CopyFrom(_key.SignData(Array.Empty())) + + // // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), + // } + // } + + // }; + // }); \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs new file mode 100644 index 0000000..3243bdf --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs @@ -0,0 +1,88 @@ +using FrostFS.Container; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using Moq; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.Refs; + +namespace FrostFS.SDK.Tests; + +public class PutContainerMock(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + PutResponse response = new() + { + Body = new PutResponse.Types.Body + { + ContainerId = new ContainerID + { + Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + } + }; + + response.VerifyHeader = PutResponseVerificationHeader(response); + + var metadata = new Metadata(); + var putContainerResponse = new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.PutAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return putContainerResponse; + }); + + return mock; + } + + private ResponseVerificationHeader PutResponseVerificationHeader(PutResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/NetmapMock.cs b/src/FrostFS.SDK.Tests/NetmapMock.cs new file mode 100644 index 0000000..0cc3889 --- /dev/null +++ b/src/FrostFS.SDK.Tests/NetmapMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Netmap; + +namespace FrostFS.SDK.Tests; + +public class NetmapMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/ObjectMock.cs b/src/FrostFS.SDK.Tests/ObjectMock.cs new file mode 100644 index 0000000..96296f8 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ObjectMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Object; + +namespace FrostFS.SDK.Tests; + +public class ObjectMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/SessionMock.cs b/src/FrostFS.SDK.Tests/SessionMock.cs new file mode 100644 index 0000000..8309fde --- /dev/null +++ b/src/FrostFS.SDK.Tests/SessionMock.cs @@ -0,0 +1,14 @@ +using Moq; +using FrostFS.Session; + +namespace FrostFS.SDK.Tests; + +public class SessionMock(string key) : ServiceBase(key) +{ + public Mock GetMock() + { + var mock = new Mock(); + + return mock; + } +} From f5d1899dd24696c7c3dc3825c2f42e3b375e9291 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 26 Jun 2024 15:15:58 +0300 Subject: [PATCH 04/65] [#13] Change GetObject result to stream Signed-off-by: Pavel Gross --- .../Services/ObjectReader.cs | 15 +++++-- .../Services/ObjectServiceProvider.cs | 43 ++++++++++++------- src/FrostFS.SDK.ModelsV2/Object/Object.cs | 2 + .../Object/ObjectAttribute.cs | 12 ++---- src/FrostFS.SDK.Tests/ClientTestLive.cs | 37 +++++++++++++--- 5 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs index 73e982a..19c65d8 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs @@ -4,14 +4,17 @@ using System.Threading.Tasks; using Grpc.Core; using FrostFS.Object; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2; -internal class ObjectReader(AsyncServerStreamingCall call) : IDisposable +public class ObjectReader(AsyncServerStreamingCall call) : IObjectReader { + private bool disposed = false; + public AsyncServerStreamingCall Call { get; private set; } = call; - public async Task ReadHeader() + internal async Task ReadHeader() { if (!await Call.ResponseStream.MoveNext()) throw new InvalidOperationException("unexpected end of stream"); @@ -45,6 +48,12 @@ internal class ObjectReader(AsyncServerStreamingCall call) : IDispo public void Dispose() { - Call.Dispose(); + if (!disposed) + { + Call.Dispose(); + GC.SuppressFinalize(this); + + disposed = true; + } } } diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 8619cdd..bf35ad7 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -80,7 +80,7 @@ internal class ObjectServiceProvider : ContextAccessor var obj = await GetObject(request, ctx); - return obj.ToModel(); + return obj; } internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) @@ -311,27 +311,38 @@ internal class ObjectServiceProvider : ContextAccessor } // TODO: add implementation with stream writer! - private async Task GetObject(GetRequest request, Context ctx) + private async Task GetObject(GetRequest request, Context ctx) { - using var stream = GetObjectInit(request, ctx); + var reader = GetObjectInit(request, ctx); - var obj = await stream.ReadHeader(); - var payload = new byte[obj.Header.PayloadLength]; - var offset = 0L; - var chunk = await stream.ReadChunk(); + var obj = await reader.ReadHeader(); - while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) - { - var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); + var @object = obj.ToModel(); - Array.Copy(chunk, 0, payload, offset, length); - offset += chunk.Length; - chunk = await stream.ReadChunk(); - } + @object.ObjectReader = reader; - obj.Payload = ByteString.CopyFrom(payload); + return @object; + + // obj. - return obj; + // return obj.ToModel(); + + // var payload = new byte[obj.Header.PayloadLength]; + // var offset = 0L; + // var chunk = await stream.ReadChunk(); + + // while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) + // { + // var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); + + // Array.Copy(chunk, 0, payload, offset, length); + // offset += chunk.Length; + // chunk = await stream.ReadChunk(); + // } + + // obj.Payload = ByteString.CopyFrom(payload); + + // return obj; } private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/Object.cs index 51c4630..e129601 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Object.cs @@ -27,6 +27,8 @@ public class Object } public byte[] Payload { get; set; } + + public IObjectReader? ObjectReader { get; set; } public Signature? Signature { get; set; } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs index b114f2b..92d38b2 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs @@ -1,13 +1,7 @@ namespace FrostFS.SDK.ModelsV2; -public class ObjectAttribute +public class ObjectAttribute(string key, string value) { - public string Key { get; set; } - public string Value { get; set; } - - public ObjectAttribute(string key, string value) - { - Key = key; - Value = value; - } + public string Key { get; set; } = key; + public string Value { get; set; } = value; } diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/ClientTestLive.cs index 70dda66..f596472 100644 --- a/src/FrostFS.SDK.Tests/ClientTestLive.cs +++ b/src/FrostFS.SDK.Tests/ClientTestLive.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; @@ -82,13 +83,17 @@ public class ClientTestLive Assert.NotNull(container); + Random rnd = new(); + var bytes = new byte[6*1024*1024 + 100]; + rnd.NextBytes(bytes); + var param = new PutObjectParameters { Header = new ObjectHeader( containerId: containerId, type: ObjectType.Regular, new ObjectAttribute("fileName", "test")), - Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + Payload = new MemoryStream(bytes), ClientCut = false }; @@ -102,7 +107,7 @@ public class ClientTestLive hasObject = true; var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); - Assert.Equal(10u, objHeader.PayloadLength); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); @@ -112,7 +117,16 @@ public class ClientTestLive var @object = await fsClient.GetObjectAsync(containerId, objectId!); - Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + byte[]? chunk = null; + while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + { + ms.Write(chunk); + } + + Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); await Cleanup(fsClient); @@ -145,13 +159,17 @@ public class ClientTestLive Assert.NotNull(container); + Random rnd = new(); + var bytes = new byte[6*1024*1024 + 100]; + rnd.NextBytes(bytes); + var param = new PutObjectParameters { Header = new ObjectHeader( containerId: containerId, type: ObjectType.Regular, new ObjectAttribute("fileName", "test")), - Payload = new MemoryStream([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), + Payload = new MemoryStream(bytes), ClientCut = true }; @@ -165,7 +183,7 @@ public class ClientTestLive hasObject = true; var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); - Assert.Equal(10u, objHeader.PayloadLength); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); @@ -175,7 +193,14 @@ public class ClientTestLive var @object = await fsClient.GetObjectAsync(containerId, objectId!); - Assert.Equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], @object.Payload); + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + byte[]? chunk = null; + while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + { + ms.Write(chunk); + } await Cleanup(fsClient); From 17492ee871b5479d2a1946218f6102b7c5ff28f6 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 26 Jun 2024 15:24:15 +0300 Subject: [PATCH 05/65] [#13] Add cancellation token to GetChank method Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs | 7 ++++--- src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs index 19c65d8..647a7ad 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs @@ -5,13 +5,14 @@ using Grpc.Core; using FrostFS.Object; using FrostFS.SDK.ModelsV2; +using System.Threading; namespace FrostFS.SDK.ClientV2; public class ObjectReader(AsyncServerStreamingCall call) : IObjectReader { private bool disposed = false; - + public AsyncServerStreamingCall Call { get; private set; } = call; internal async Task ReadHeader() @@ -32,9 +33,9 @@ public class ObjectReader(AsyncServerStreamingCall call) : IObjectR }; } - public async Task ReadChunk() + public async Task ReadChunk(CancellationToken cancellationToken = default) { - if (!await Call.ResponseStream.MoveNext()) + if (!await Call.ResponseStream.MoveNext(cancellationToken)) return null; var response = Call.ResponseStream.Current; diff --git a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs index 3822dce..4c5ce3d 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs @@ -1,9 +1,10 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace FrostFS.SDK.ModelsV2; public interface IObjectReader : IDisposable { - Task ReadChunk(); + Task ReadChunk(CancellationToken cancellationToken = default); } From 605463ec24f98d1245717e12e5257bcdc129977b Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 27 Jun 2024 12:38:14 +0300 Subject: [PATCH 06/65] [#13] Drop comments Signed-off-by: Pavel Gross --- .../Services/ObjectServiceProvider.cs | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index bf35ad7..01999a5 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -310,7 +310,6 @@ internal class ObjectServiceProvider : ContextAccessor return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - // TODO: add implementation with stream writer! private async Task GetObject(GetRequest request, Context ctx) { var reader = GetObjectInit(request, ctx); @@ -321,28 +320,7 @@ internal class ObjectServiceProvider : ContextAccessor @object.ObjectReader = reader; - return @object; - - // obj. - - // return obj.ToModel(); - - // var payload = new byte[obj.Header.PayloadLength]; - // var offset = 0L; - // var chunk = await stream.ReadChunk(); - - // while (chunk is not null && (ulong)offset < obj.Header.PayloadLength) - // { - // var length = Math.Min((long)obj.Header.PayloadLength - offset, chunk.Length); - - // Array.Copy(chunk, 0, payload, offset, length); - // offset += chunk.Length; - // chunk = await stream.ReadChunk(); - // } - - // obj.Payload = ByteString.CopyFrom(payload); - - // return obj; + return @object; } private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) From ae67b12313b0e8b5ee4ee4aa2879fc48c2aaf235 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 1 Jul 2024 11:56:47 +0300 Subject: [PATCH 07/65] [#14] Add interceptors Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 196 +++++++++++++----- .../Interceptors/MetricsInterceptor.cs | 73 +++++++ .../Interfaces/IFrostFSClient.cs | 29 ++- .../Mappers/Session/Session.cs | 6 +- .../Services/ContainerServiceProvider.cs | 4 - .../Services/ObjectServiceProvider.cs | 160 +++----------- .../Services/ObjectTools.cs | 104 ++++++++++ .../Tools/ClientEnvironment.cs | 14 +- src/FrostFS.SDK.ModelsV2/CallStatistics.cs | 7 + src/FrostFS.SDK.ModelsV2/Context.cs | 13 ++ .../FrostFS.SDK.ModelsV2.csproj | 4 + .../FrostFS.SDK.ProtosV2.csproj | 2 +- src/FrostFS.SDK.Tests/ClientTestLive.cs | 108 ++++++---- .../ContainerServiceBase.cs | 28 --- .../DeleteContainerMock.cs | 69 ------ .../ContainerServiceMocks/GetContainerMock.cs | 163 --------------- .../{ClientTest.cs => ContainerTest.cs} | 58 ++++-- .../ContainerServiceBase.cs | 78 +++++++ .../DeleteContainerMock.cs | 54 +++++ .../GetContainerMock copy.cs | 13 ++ .../ContainerServiceMocks/GetContainerMock.cs | 58 ++++++ .../ContainerServiceMocks/PutContainerMock.cs | 2 +- .../{ => Mocks}/NetmapMock.cs | 2 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 101 +++++++++ src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 57 +++++ src/FrostFS.SDK.Tests/ObjectMock.cs | 14 -- src/FrostFS.SDK.Tests/ObjectTest.cs | 66 ++++++ src/FrostFS.SDK.Tests/SessionMock.cs | 14 -- 28 files changed, 943 insertions(+), 554 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs create mode 100644 src/FrostFS.SDK.ModelsV2/CallStatistics.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs delete mode 100644 src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs rename src/FrostFS.SDK.Tests/{ClientTest.cs => ContainerTest.cs} (57%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs rename src/FrostFS.SDK.Tests/{ => Mocks}/ContainerServiceMocks/PutContainerMock.cs (97%) rename src/FrostFS.SDK.Tests/{ => Mocks}/NetmapMock.cs (78%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/SessionMock.cs delete mode 100644 src/FrostFS.SDK.Tests/ObjectMock.cs create mode 100644 src/FrostFS.SDK.Tests/ObjectTest.cs delete mode 100644 src/FrostFS.SDK.Tests/SessionMock.cs diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 2b87c46..ca1acea 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -7,11 +7,13 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.Session; using Grpc.Core; +using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Http; +using System.Security.Cryptography; using System.Threading.Tasks; using Version = FrostFS.SDK.ModelsV2.Version; @@ -20,10 +22,15 @@ namespace FrostFS.SDK.ClientV2; public class Client : IFrostFSClient { private bool isDisposed; + + internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; } + internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; } + internal SessionService.SessionServiceClient? SessionServiceClient { get; set; } + internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } internal ClientEnvironment ClientCtx { get; set; } - public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions channelOptions) + public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { return new Client(clientOptions, channelOptions); } @@ -32,7 +39,7 @@ public class Client : IFrostFSClient /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. /// /// Global setting for client - /// Setting for gRPC cjannel + /// Setting for gRPC channel /// ContainerService.ContainerServiceClient implementation /// Netmap.NetmapService.NetmapServiceClient implementation /// Session.SessionService.SessionServiceClient implementation @@ -61,18 +68,19 @@ public class Client : IFrostFSClient OwnerId.FromKey(ecdsaKey); ClientCtx = new ClientEnvironment( + this, key: ecdsaKey, owner: OwnerId.FromKey(ecdsaKey), channel: InitGrpcChannel(settings.Value.Host, channelOptions), version: new Version(2, 13)); - ClientCtx.ContainerService = new ContainerServiceProvider(containerService, ClientCtx); - ClientCtx.NetmapService = new NetmapServiceProvider(netmapService, ClientCtx); - ClientCtx.SessionService = new SessionServiceProvider(sessionService, ClientCtx); - ClientCtx.ObjectService = new ObjectServiceProvider(objectService, ClientCtx); + ContainerServiceClient = containerService; + NetmapServiceClient = netmapService; + SessionServiceClient = sessionService; + ObjectServiceClient = objectService; } - private Client(IOptions options, GrpcChannelOptions channelOptions) + private Client(IOptions options, GrpcChannelOptions? channelOptions) { var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); @@ -83,16 +91,12 @@ public class Client : IFrostFSClient var channel = InitGrpcChannel(clientSettings.Host, channelOptions); ClientCtx = new ClientEnvironment( + this, key: ecdsaKey, owner: OwnerId.FromKey(ecdsaKey), channel: channel, version: new Version(2, 13)); - ClientCtx.ContainerService = new ContainerServiceProvider(new ContainerService.ContainerServiceClient(channel), ClientCtx); - ClientCtx.NetmapService = new NetmapServiceProvider(new NetmapService.NetmapServiceClient(channel), ClientCtx); - ClientCtx.SessionService = new SessionServiceProvider(new SessionService.SessionServiceClient(channel), ClientCtx); - ClientCtx.ObjectService = new ObjectServiceProvider(new ObjectService.ObjectServiceClient(channel), ClientCtx); - CheckFrostFsVersionSupport(); } @@ -111,72 +115,81 @@ public class Client : IFrostFSClient } } - public GrpcChannel Channel => ClientCtx.Channel; - + #region ContainerImplementation public Task GetContainerAsync(ContainerId containerId, Context? ctx = null) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.GetContainerAsync(containerId, ctx!); + var service = GetContainerService(ref ctx); + return service.GetContainerAsync(containerId, ctx!); } public IAsyncEnumerable ListContainersAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.ListContainersAsync(ctx!); + var service = GetContainerService(ref ctx); + return service.ListContainersAsync(ctx!); } public Task CreateContainerAsync(ModelsV2.Container container, Context? ctx = null) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.CreateContainerAsync(container, ctx!); + var service = GetContainerService(ref ctx); + return service.CreateContainerAsync(container, ctx!); } public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ContainerService!.DeleteContainerAsync(containerId, ctx!); + var service = GetContainerService(ref ctx); + return service.DeleteContainerAsync(containerId, ctx!); } + #endregion + #region NetworkImplementation public Task GetNetmapSnapshotAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.NetmapService!.GetNetmapSnapshotAsync(ctx!); + var service = GetNetmapService(ref ctx); + return service.GetNetmapSnapshotAsync(ctx!); } public Task GetNodeInfoAsync(Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + var service = GetNetmapService(ref ctx); + return service.GetLocalNodeInfoAsync(ctx!); } + public Task GetNetworkSettingsAsync(Context? ctx = default) + { + var service = GetNetmapService(ref ctx); + return service.GetNetworkSettingsAsync(ctx!); + } + #endregion + + #region ObjectImplementation public Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.GetObjectHeadAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.GetObjectHeadAsync(containerId, objectId, ctx!); } public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.GetObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.GetObjectAsync(containerId, objectId, ctx!); } public Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.PutObjectAsync(putObjectParameters, ctx!); + var service = GetObjectService(ref ctx); + return service.PutObjectAsync(putObjectParameters, ctx!); } public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.PutSingleObjectAsync(obj, ctx!); + var service = GetObjectService(ref ctx); + return service.PutSingleObjectAsync(obj, ctx!); } public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.DeleteObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(ref ctx); + return service.DeleteObjectAsync(containerId, objectId, ctx!); } public IAsyncEnumerable SearchObjectsAsync( @@ -184,19 +197,38 @@ public class Client : IFrostFSClient IEnumerable filters, Context? ctx = default) { - ValidateEnvironment(ref ctx); - return ClientCtx.ObjectService!.SearchObjectsAsync(containerId, filters, ctx!); + var service = GetObjectService(ref ctx); + return service.SearchObjectsAsync(containerId, filters, ctx!); + } + #endregion + + #region SessionImplementation + public async Task CreateSessionAsync(ulong expiration, Context? ctx = null) + { + var session = await CreateSessionInternalAsync(expiration, ctx); + var token = session.Serialize(); + + return new ModelsV2.SessionToken([], token); } + public Task CreateSessionInternalAsync(ulong expiration, Context? ctx = null) + { + var service = GetSessionService(ref ctx); + return service.CreateSessionAsync(expiration, ctx!); + } + #endregion + + #region ToolsImplementation public ObjectId CalculateObjectId(ObjectHeader header) - { - return ClientCtx.ObjectService!.CalculateObjectId(header); + { + return new ObjectTools(ClientCtx).CalculateObjectId(header); } + #endregion private async void CheckFrostFsVersionSupport(Context? ctx = default) { - ValidateEnvironment(ref ctx); - var localNodeInfo = await ClientCtx.NetmapService!.GetLocalNodeInfoAsync(ctx!); + var service = GetNetmapService(ref ctx); + var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!); if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { @@ -206,15 +238,76 @@ public class Client : IFrostFSClient } } - private void ValidateEnvironment(ref Context? ctx) + private CallInvoker? SetupEnvironment(ref Context? ctx) { if (isDisposed) throw new Exception("Client is disposed."); - if (ClientCtx == null || !ClientCtx.Initialized) - throw new Exception("Client is not initialized."); - ctx ??= new Context(); + + CallInvoker? callInvoker = null; + if (ctx.Interceptors != null && ctx.Interceptors.Count > 0) + { + foreach (var interceptor in ctx.Interceptors) + { + callInvoker = AddInvoker(callInvoker, interceptor); + } + } + + if (ctx.Callback != null) + callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Callback)); + + return callInvoker; + + CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor) + { + if (callInvoker == null) + callInvoker = ClientCtx.Channel.Intercept(interceptor); + else + callInvoker.Intercept(interceptor); + + return callInvoker; + } + } + + private NetmapServiceProvider GetNetmapService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = NetmapServiceClient ?? (callInvoker != null + ? new NetmapService.NetmapServiceClient(callInvoker) + : new NetmapService.NetmapServiceClient(ClientCtx.Channel)); + + return new NetmapServiceProvider(client, ClientCtx); + } + + private SessionServiceProvider GetSessionService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = SessionServiceClient ?? (callInvoker != null + ? new SessionService.SessionServiceClient(callInvoker) + : new SessionService.SessionServiceClient(ClientCtx.Channel)); + + return new SessionServiceProvider(client, ClientCtx); + } + + private ContainerServiceProvider GetContainerService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = ContainerServiceClient ?? (callInvoker != null + ? new ContainerService.ContainerServiceClient(callInvoker) + : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); + + return new ContainerServiceProvider(client, ClientCtx); + } + + private ObjectServiceProvider GetObjectService(ref Context? ctx) + { + var callInvoker = SetupEnvironment(ref ctx); + var client = ObjectServiceClient ?? (callInvoker != null + ? new ObjectService.ObjectServiceClient(callInvoker) + : new ObjectService.ObjectServiceClient(ClientCtx.Channel)); + + return new ObjectServiceProvider(client, ClientCtx); } private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) @@ -230,21 +323,14 @@ public class Client : IFrostFSClient throw new ArgumentException(msg); } - ChannelCredentials grpcCredentials = uri.Scheme switch - { - "https" => ChannelCredentials.SecureSsl, - "http" => ChannelCredentials.Insecure, - _ => throw new ArgumentException($"Host '{host}' has invalid URI scheme: '{uri.Scheme}'.") - }; - if (channelOptions != null) { return GrpcChannel.ForAddress(uri, channelOptions); } - + + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions { - Credentials = grpcCredentials, HttpHandler = new HttpClientHandler() }); } diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs new file mode 100644 index 0000000..c395629 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs @@ -0,0 +1,73 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using FrostFS.SDK.ModelsV2; +using Grpc.Core; +using Grpc.Core.Interceptors; + +namespace FrostFS.SDK.ClientV2; + +public class MetricsInterceptor(Action callback) : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + public override AsyncClientStreamingCall AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation) + { + var call = continuation(context); + + return new AsyncClientStreamingCall( + call.RequestStream, + HandleStreamResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); + + return response; + } + + private async Task HandleStreamResponse(AsyncClientStreamingCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); + + return response; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 4234a1d..df5bcce 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -4,12 +4,24 @@ using System.Threading.Tasks; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; -using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable { + #region Network + Task GetNetmapSnapshotAsync(Context? context = default); + + Task GetNodeInfoAsync(Context? context = default); + + Task GetNetworkSettingsAsync(Context? context = default); + #endregion + + #region Session + Task CreateSessionAsync(ulong expiration, Context? context = default); + #endregion + + #region Container Task GetContainerAsync(ContainerId containerId, Context? context = default); IAsyncEnumerable ListContainersAsync(Context? context = default); @@ -17,7 +29,9 @@ public interface IFrostFSClient : IDisposable Task CreateContainerAsync(ModelsV2.Container container, Context? context = default); Task DeleteContainerAsync(ContainerId containerId, Context? context = default); - + #endregion + + #region Object Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); @@ -29,13 +43,10 @@ public interface IFrostFSClient : IDisposable Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); IAsyncEnumerable SearchObjectsAsync(ContainerId cid, IEnumerable filters, Context? context = default); - - Task GetNetmapSnapshotAsync(Context? context = default); - - Task GetNodeInfoAsync(Context? context = default); - + #endregion + + #region Tools ObjectId CalculateObjectId(ObjectHeader header); - - GrpcChannel Channel { get; } + #endregion } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs index 402f7e1..4b85759 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -2,17 +2,17 @@ using System; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.ClientV2; public static class SessionMapper { - internal static string SerializeSessionToken(this Session.SessionToken token) + internal static byte[] Serialize(this Session.SessionToken token) { byte[] bytes = new byte[token.CalculateSize()]; CodedOutputStream stream = new(bytes); token.WriteTo(stream); - return Convert.ToBase64String(bytes); + return bytes; } internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes) diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index b2d72ee..b2aadad 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -80,10 +80,6 @@ internal class ContainerServiceProvider : ContextAccessor request.Sign(Context.Key); var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); - - //var response = await Context.InvokeAsyncUnaryWithMetrics(() => - // containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken), - // nameof(containerServiceClient.PutAsync)); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 01999a5..2439c6e 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Threading.Tasks; using Google.Protobuf; -using System.Threading.Tasks; - using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -17,15 +15,9 @@ using FrostFS.SDK.ClientV2.Extensions; namespace FrostFS.SDK.ClientV2; -internal class ObjectServiceProvider : ContextAccessor +internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx) { - private readonly ObjectService.ObjectServiceClient objectServiceClient; - - internal ObjectServiceProvider(ObjectService.ObjectServiceClient objectServiceClient, ClientEnvironment context) - : base (context) - { - this.objectServiceClient = objectServiceClient; - } + readonly ObjectTools tools = new(ctx); internal async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx) { @@ -44,7 +36,7 @@ internal class ObjectServiceProvider : ContextAccessor request.AddMetaHeader(); request.Sign(Context.Key); - var response = await objectServiceClient!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -97,45 +89,38 @@ internal class ObjectServiceProvider : ContextAccessor return PutStreamObject(parameters, ctx); } - internal async Task PutSingleObjectAsync(ModelsV2.Object @object, Context ctx) + internal async Task PutSingleObjectAsync(ModelsV2.Object modelObject, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); - var obj = CreateObject(@object); + var grpcObject = tools.CreateObject(modelObject); var request = new PutSingleRequest { Body = new PutSingleRequest.Types.Body() { - Object = obj + Object = grpcObject } }; request.AddMetaHeader(); request.AddObjectSessionToken( sessionToken, - obj.Header.ContainerId, - obj.ObjectId, + grpcObject.Header.ContainerId, + grpcObject.ObjectId, ObjectSessionContext.Types.Verb.Put, Context.Key ); request.Sign(Context.Key); - var response = await objectServiceClient!.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); - return ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()); + return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } - - internal ObjectId CalculateObjectId(ObjectHeader header) - { - var grpcHeader = CreateHeader(header, []); - - return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); - } - + internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { var request = new DeleteRequest @@ -153,7 +138,7 @@ internal class ObjectServiceProvider : ContextAccessor request.AddMetaHeader(); request.Sign(Context.Key); - var response = await objectServiceClient!.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); } @@ -195,7 +180,7 @@ internal class ObjectServiceProvider : ContextAccessor List sentObjectIds = []; ModelsV2.Object? currentObject; - var networkSettings = await Context.NetmapService!.GetNetworkSettingsAsync(ctx); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); var partSize = (int)networkSettings.MaxObjectSize; var buffer = new byte[partSize]; @@ -215,7 +200,6 @@ internal class ObjectServiceProvider : ContextAccessor largeObject.AppendBlock(buffer, bytesCount); currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) - .AddAttributes(header.Attributes) .SetSplit(split); if (largeObject.PayloadLength == fullLength) @@ -228,6 +212,7 @@ internal class ObjectServiceProvider : ContextAccessor if (sentObjectIds.Count != 0) { + largeObject.AddAttributes(parameters.Header!.Attributes); largeObject.CalculateHash(); currentObject.SetParent(largeObject); @@ -238,11 +223,14 @@ internal class ObjectServiceProvider : ContextAccessor var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) .AddChildren(sentObjectIds); + linkObject.Header.Attributes.Clear(); + _ = await PutSingleObjectAsync(linkObject, ctx); - return CalculateObjectId(largeObject.Header); + return tools.CalculateObjectId(largeObject.Header); } + currentObject.AddAttributes(parameters.Header!.Attributes); return await PutSingleObjectAsync(currentObject, ctx); } @@ -257,10 +245,7 @@ internal class ObjectServiceProvider : ContextAccessor hdr.OwnerId = Context.Owner.ToGrpcMessage(); hdr.Version = Context.Version.ToGrpcMessage(); - var oid = new ObjectID - { - Value = hdr.Sha256() - }; + var oid = new ObjectID { Value = hdr.Sha256() }; var request = new PutRequest { @@ -269,7 +254,7 @@ internal class ObjectServiceProvider : ContextAccessor Init = new PutRequest.Types.Body.Types.Init { Header = hdr - }, + } } }; @@ -314,13 +299,12 @@ internal class ObjectServiceProvider : ContextAccessor { var reader = GetObjectInit(request, ctx); - var obj = await reader.ReadHeader(); + var grpcObject = await reader.ReadHeader(); + var modelObject = grpcObject.ToModel(); + + modelObject.ObjectReader = reader; - var @object = obj.ToModel(); - - @object.ObjectReader = reader; - - return @object; + return modelObject; } private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) @@ -328,7 +312,7 @@ internal class ObjectServiceProvider : ContextAccessor if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); - var call = objectServiceClient!.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); return new ObjectReader(call); } @@ -340,7 +324,7 @@ internal class ObjectServiceProvider : ContextAccessor throw new ArgumentNullException(nameof(initRequest)); } - var call = objectServiceClient!.Put(null, ctx.Deadline, ctx.CancellationToken); + var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); await call.RequestStream.WriteAsync(initRequest); @@ -372,98 +356,16 @@ internal class ObjectServiceProvider : ContextAccessor throw new ArgumentNullException(nameof(initRequest)); } - var call = objectServiceClient!.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); return new SearchReader(call); - } - - private Object.Object CreateObject(ModelsV2.Object @object) - { - var grpcHeader = @object.Header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); - - if (@object.Payload != null) - { - grpcHeader.PayloadLength = (ulong)@object.Payload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); - } - - var split = @object.Header.Split; - if (split != null) - { - grpcHeader.Split = new Header.Types.Split - { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null - }; - - if (split.Children != null && split.Children.Count != 0) - grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); - - if (split.ParentHeader is not null) - { - var grpcParentHeader = CreateHeader(split.ParentHeader, []); - - grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; - grpcHeader.Split.ParentHeader = grpcParentHeader; - grpcHeader.Split.ParentSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), - }; - - split.Parent = grpcHeader.Split.Parent.ToModel(); - } - - grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); - } - - var obj = new Object.Object - { - Header = grpcHeader, - ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, - Payload = ByteString.CopyFrom(@object.Payload) - }; - - obj.Signature = new Refs.Signature - { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), - }; - - return obj; - } - - private Header CreateHeader(ObjectHeader header, byte[]? payload) - { - var grpcHeader = header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); - - if (header.PayloadCheckSum != null) - grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); - else if (payload != null) - grpcHeader.PayloadHash = Sha256Checksum(payload); - - return grpcHeader; - } - - private static Checksum Sha256Checksum(byte[] data) - { - return new Checksum - { - Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(data.Sha256()) - }; - } + } private async Task GetOrCreateSession(Context ctx) { if (string.IsNullOrEmpty(ctx.SessionToken)) { - return await Context.SessionService!.CreateSessionAsync(uint.MaxValue, ctx); + return await Context.Client.CreateSessionInternalAsync(uint.MaxValue, ctx); } return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs new file mode 100644 index 0000000..95ba626 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs @@ -0,0 +1,104 @@ +using System.Linq; + +using Google.Protobuf; + +using FrostFS.Object; +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2; + +internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) +{ + internal ObjectId CalculateObjectId(ObjectHeader header) + { + var grpcHeader = CreateHeader(header, []); + + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + } + + internal Object.Object CreateObject(ModelsV2.Object @object) + { + var grpcHeader = @object.Header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (@object.Payload != null) + { + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + } + + var split = @object.Header.Split; + if (split != null) + { + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; + + if (split.Children != null && split.Children.Count != 0) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, []); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + + grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); + } + + var obj = new Object.Object + { + Header = grpcHeader, + ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + Payload = ByteString.CopyFrom(@object.Payload) + }; + + obj.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Context.Key.PublicKey()), + Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), + }; + + return obj; + } + + internal Header CreateHeader(ObjectHeader header, byte[]? payload) + { + var grpcHeader = header.ToGrpcMessage(); + + grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); + grpcHeader.Version = Context.Version.ToGrpcMessage(); + + if (header.PayloadCheckSum != null) + grpcHeader.PayloadHash = Sha256Checksum(header.PayloadCheckSum); + else if (payload != null) + grpcHeader.PayloadHash = Sha256Checksum(payload); + + return grpcHeader; + } + + internal static Checksum Sha256Checksum(byte[] data) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(data.Sha256()) + }; + } + +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 037078b..b37e7fc 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,3 +1,4 @@ +using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; using Grpc.Net.Client; using System; @@ -5,7 +6,7 @@ using System.Security.Cryptography; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable { internal OwnerId Owner { get; } = owner; internal GrpcChannel Channel { get; private set; } = channel; @@ -13,16 +14,7 @@ public class ClientEnvironment(ECDsa key, OwnerId owner, GrpcChannel channel, Mo internal ModelsV2.Version Version { get; } = version; internal NetworkSettings? NetworkSettings { get; set; } - internal ContainerServiceProvider? ContainerService { get; set; } - internal NetmapServiceProvider? NetmapService { get; set; } - internal SessionServiceProvider? SessionService { get; set; } - internal ObjectServiceProvider? ObjectService { get; set; } - - internal bool Initialized => - ContainerService != null - && NetmapService != null - && SessionService != null - && ObjectService != null; + internal Client Client { get; set; } = client; public void Dispose() { diff --git a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs new file mode 100644 index 0000000..e6424b6 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK.ModelsV2; + +public class CallStatistics +{ + public string MethodName { get; set; } + public long ElapsedMicroSeconds { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs index 302dafc..e486464 100644 --- a/src/FrostFS.SDK.ModelsV2/Context.cs +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -1,13 +1,26 @@ using System; +using System.Collections.Generic; using System.Threading; +using FrostFS.SDK.ModelsV2; +using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; public class Context() { + private List interceptors; + public CancellationToken CancellationToken { get; set; } = default; public TimeSpan Timeout { get; set; } = default; public string SessionToken { get; set; } = string.Empty; public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; + + public Action Callback { get; set; } + + public List Interceptors + { + get { return interceptors ??= []; } + set { interceptors = value; } + } } diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index 0f47676..be180b0 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -9,6 +9,10 @@ true + + + + diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj index 0a559ed..0a28822 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/ClientTestLive.cs index f596472..bfd50ca 100644 --- a/src/FrostFS.SDK.Tests/ClientTestLive.cs +++ b/src/FrostFS.SDK.Tests/ClientTestLive.cs @@ -7,6 +7,8 @@ using FrostFS.SDK.ModelsV2.Netmap; using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Options; +using Grpc.Core.Interceptors; +using System.Diagnostics; namespace FrostFS.SDK.Tests; @@ -18,13 +20,7 @@ public class ClientTestLive [Fact] public async void NetworkMapTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); var result = await fsClient.GetNetmapSnapshotAsync(); @@ -36,20 +32,14 @@ public class ClientTestLive Assert.Equal(13, item.Version.Minor); Assert.Equal(NodeState.Online, item.State); Assert.True(item.PublicKey.Length > 0); - Assert.Single(item.Addresses); + Assert.Single(item.Addresses); Assert.Equal(9, item.Attributes.Count); } [Fact] public async void NodeInfoTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); var result = await fsClient.GetNodeInfoAsync(); @@ -64,27 +54,30 @@ public class ClientTestLive [Fact] public async void SimpleScenarioTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); - await Cleanup(fsClient); var containerId = await fsClient.CreateContainerAsync( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))), + new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + } + ); - var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10), + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); Random rnd = new(); - var bytes = new byte[6*1024*1024 + 100]; + var bytes = new byte[6 * 1024 * 1024 + 100]; rnd.NextBytes(bytes); var param = new PutObjectParameters @@ -97,7 +90,10 @@ public class ClientTestLive ClientCut = false }; - var objectId = await fsClient.PutObjectAsync(param); + var objectId = await fsClient.PutObjectAsync(param, new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }); var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); @@ -121,7 +117,7 @@ public class ClientTestLive MemoryStream ms = new(downloadedBytes); byte[]? chunk = null; - while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk); } @@ -141,26 +137,28 @@ public class ClientTestLive [Fact] public async void ClientCutScenarioTest() { - var channelOptions = new GrpcChannelOptions - { - Credentials = ChannelCredentials.Insecure, - HttpHandler = new HttpClientHandler() - }; - - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url), channelOptions); + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); await Cleanup(fsClient); var containerId = await fsClient.CreateContainerAsync( new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); - var context = new Context { Timeout = TimeSpan.FromSeconds(10) }; + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10) + }; + + var metrics = new MetricsInterceptor(); + + context.Interceptors.Add(metrics); + var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); Random rnd = new(); - var bytes = new byte[6*1024*1024 + 100]; + var bytes = new byte[6 * 1024 * 1024 + 100]; rnd.NextBytes(bytes); var param = new PutObjectParameters @@ -193,11 +191,11 @@ public class ClientTestLive var @object = await fsClient.GetObjectAsync(containerId, objectId!); - var downloadedBytes = new byte[@object.Header.PayloadLength]; + var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); byte[]? chunk = null; - while ((chunk = await @object.ObjectReader.ReadChunk()) != null) + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk); } @@ -237,7 +235,7 @@ public class ClientTestLive { try { - await Task.Delay(100); + await Task.Delay(100); return await fsClient.GetContainerAsync(id, ctx); } catch (ApplicationException) @@ -252,3 +250,35 @@ public class ClientTestLive } } } + +public class MetricsInterceptor() : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + return response; + } +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs deleted file mode 100644 index bbf1476..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/ContainerServiceBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Security.Cryptography; -using FrostFS.Container; -using Moq; - -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; - -namespace FrostFS.SDK.Tests; - -public abstract class ServiceBase(string key) -{ - public ECDsa Key { get; private set; } = key.LoadWif(); - public ModelsV2.Version Version { get; set; } = DefaultVersion; - public BasicAcl Acl { get; set; } = DefaultAcl; - public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; - - public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); - public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; - public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); -} - -public abstract class ContainerServiceBase(string key) : ServiceBase (key) -{ - public Guid ContainerGuid { get; set; } = Guid.NewGuid(); - - public abstract Mock GetMock(); -} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs deleted file mode 100644 index a752d10..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/DeleteContainerMock.cs +++ /dev/null @@ -1,69 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; - -namespace FrostFS.SDK.Tests; - -public class DeleteContainerMock(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var v = mock.Setup(x => x.DeleteAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())); - - - v.Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - var deleteResponse = new Object.DeleteResponse - { - Body = new Object.DeleteResponse.Types.Body - { - Tombstone = new Refs.Address - { - ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - } - }, - MetaHeader = new ResponseMetaHeader() - }; - - var metadata = new Metadata(); - - return new AsyncUnaryCall( - Task.FromResult(deleteResponse), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - }); - - return mock; - } -} - - - // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => - // { - // return new - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // } - // }; - // }); - - - \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs deleted file mode 100644 index 61feb5e..0000000 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/GetContainerMock.cs +++ /dev/null @@ -1,163 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; - -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC; - -namespace FrostFS.SDK.Tests; - -public class GetContainerMock(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var grpcVersion = Version.ToGrpcMessage(); - - var getResponse = new GetResponse - { - Body = new GetResponse.Types.Body - { - Container = new Container.Container - { - Version = grpcVersion, - Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), - BasicAcl = (uint)Acl, - PlacementPolicy = PlacementPolicy.ToGrpcMessage() - } - }, - MetaHeader = new ResponseMetaHeader - { - Version = grpcVersion, - Epoch = 100, - Ttl = 1 - } - }; - - getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); - - var metadata = new Metadata(); - var getContainerResponse = new AsyncUnaryCall( - Task.FromResult(getResponse), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - - mock.Setup(x => x.GetAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Verifier.CheckRequest(r); - - return getContainerResponse; - }); - - return mock; - } - - private ResponseVerificationHeader GetResponseVerificationHeader(GetResponse response) - { - var verifyHeader = new ResponseVerificationHeader - { - MetaSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) - }, - BodySignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) - }, - OriginSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = FrostFS.Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData([])) - } - }; - - return verifyHeader; - } -} - -// objectServiceClientMock.Setup( -// x => x.Put(It.IsAny(), It.IsAny(), It.IsAny())) -// .Returns((Metadata m, DateTime dt, CancellationToken ct) => -// { -// return new AsyncClientStreamingCall(null, null, null, null, null, null); - -// //IClientStreamWriter requestStream, Task responseAsync, Task responseHeadersAsync, Func getStatusFunc, Func getTrailersFunc, Action disposeAction -// }); - -// return objectServiceClientMock; -// } - - -// } - - - // public virtual global::FrostFS.Object.HeadResponse Head(global::FrostFS.Object.HeadRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken)) - // { - // return Head(request, new grpc::CallOptions(headers, deadline, cancellationToken)); - // } - - // objectServiceClientMock.Setup(x => x.Head(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime dt, CancellationToken ct) => - // { - // return new - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // } - // }; - // }); - - - // objectServiceClientMock.Setup(x => x.Delete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - // .Returns((Object.DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - // { - // return new Object.DeleteResponse - // { - // Body = new Object.DeleteResponse.Types.Body - // { - // Tombstone = new Refs.Address - // { - // ContainerId = new Refs.ContainerID { Value = ByteString.CopyFrom([1, 2, 3]) }, - // ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom([4, 5, 6]) } - // } - // }, - // MetaHeader = new ResponseMetaHeader() - // { - // }, - // VerifyHeader = new ResponseVerificationHeader() - // { - // MetaSignature = new Refs.Signature - // { - // Key = ByteString.CopyFrom(_key.PublicKey()), - // Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - // Sign = ByteString.CopyFrom(_key.SignData(Array.Empty())) - - // // ByteString.CopyFrom(_key.SignData(grpcHeader.Split.Parent.ToByteArray())), - // } - // } - - // }; - // }); \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ClientTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs similarity index 57% rename from src/FrostFS.SDK.Tests/ClientTest.cs rename to src/FrostFS.SDK.Tests/ContainerTest.cs index e543b7f..b915304 100644 --- a/src/FrostFS.SDK.Tests/ClientTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -3,18 +3,20 @@ using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + using Microsoft.Extensions.Options; namespace FrostFS.SDK.Tests; -public class ClientTest +public class ContainerTest { private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; [Fact] public async void CreateContainerTest() { - var factory = new PutContainerMock(this.key) + var factory = new PutContainerMockFactory(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), @@ -30,10 +32,10 @@ public class ClientTest var fsClient = Client.GetTestInstance( settings, null, - new NetmapMock(this.key).GetMock().Object, - new SessionMock(this.key).GetMock().Object, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, factory.GetMock().Object, - new ObjectMock(this.key).GetMock().Object); + new ObjectMockFactory(this.key).GetMock().Object); var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); @@ -45,7 +47,7 @@ public class ClientTest [Fact] public async void GetContainerTest() { - var factory = new GetContainerMock(this.key) + var factory = new GetContainerMockFactory(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), @@ -62,10 +64,10 @@ public class ClientTest var fsClient = Client.GetTestInstance( settings, null, - new NetmapMock(this.key).GetMock().Object, - new SessionMock(this.key).GetMock().Object, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, factory.GetMock().Object, - new ObjectMock(this.key).GetMock().Object); + new ObjectMockFactory(this.key).GetMock().Object); var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); @@ -80,8 +82,38 @@ public class ClientTest Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); } - // [Fact] - // public async void DeleteObjectAsyncTest() - // { - // } + [Fact] + public async void DeleteContainerAsyncTest() + { + var factory = new DeleteContainerMockFactory(this.key) + { + Version = new ModelsV2.Version(2, 13), + Acl = BasicAcl.PublicRW, + ContainerGuid = Guid.NewGuid(), + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, + factory.GetMock().Object, + new ObjectMockFactory(this.key).GetMock().Object); + + var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); + + await fsClient.DeleteContainerAsync(cid); + + Assert.Single(factory.Requests); + + var request = factory.Requests.First(); + + Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); + } } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs new file mode 100644 index 0000000..0ea346d --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -0,0 +1,78 @@ +using System.Security.Cryptography; +using FrostFS.Container; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2.Enums; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.Session; +using Google.Protobuf; + +using FrostFS.SDK.ClientV2; +using FrostFS.Object; +using Grpc.Core; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public abstract class ServiceBase(string key) +{ + public ECDsa Key { get; private set; } = key.LoadWif(); + public ModelsV2.Version Version { get; set; } = DefaultVersion; + public BasicAcl Acl { get; set; } = DefaultAcl; + public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; + + public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); + public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; + public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); + + public Metadata ResponseMetaData => []; + + protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) + { + var verifyHeader = new ResponseVerificationHeader + { + MetaSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + }, + BodySignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + }, + OriginSignature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, + Sign = ByteString.CopyFrom(Key.SignData([])) + } + }; + + return verifyHeader; + } + + public ResponseMetaHeader ResponseMetaHeader => new() + { + Version = Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + }; +} + +public abstract class ContainerServiceBase(string key) : ServiceBase (key) +{ + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); + + public abstract Mock GetMock(); +} + +public abstract class ObjectServiceBase(string key) : ServiceBase (key) +{ + public abstract Mock GetMock(); + + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs new file mode 100644 index 0000000..ba03978 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs @@ -0,0 +1,54 @@ +using FrostFS.Container; +using FrostFS.Session; +using Grpc.Core; +using Moq; + +namespace FrostFS.SDK.Tests; + +public class DeleteContainerMockFactory(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Requests.Add(new RequestData(r, m, dt, ct)); + + var response = new DeleteResponse + { + Body = new DeleteResponse.Types.Body(), + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + }); + + return mock; + } + + public List> Requests = []; +} + +public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) +{ + public T Request => request; + public Metadata Metadata => m; + public DateTime? deadline => dt; + public CancellationToken CancellationToken => ct; +} + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs new file mode 100644 index 0000000..3a17e88 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs @@ -0,0 +1,13 @@ +using FrostFS.Container; +using Moq; + + +namespace FrostFS.SDK.Tests; + +public class ContainerStub(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + return new Mock(); + } +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs new file mode 100644 index 0000000..8b2a496 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -0,0 +1,58 @@ +using FrostFS.Container; +using Google.Protobuf; +using Grpc.Core; +using Moq; + +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.Tests; + +public class GetContainerMockFactory(string key) : ContainerServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + var grpcVersion = Version.ToGrpcMessage(); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Container = new Container.Container + { + Version = grpcVersion, + Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), + BasicAcl = (uint)Acl, + PlacementPolicy = PlacementPolicy.ToGrpcMessage() + } + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.GetAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs similarity index 97% rename from src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs rename to src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs index 3243bdf..c6abeab 100644 --- a/src/FrostFS.SDK.Tests/ContainerServiceMocks/PutContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs @@ -11,7 +11,7 @@ using FrostFS.Refs; namespace FrostFS.SDK.Tests; -public class PutContainerMock(string key) : ContainerServiceBase(key) +public class PutContainerMockFactory(string key) : ContainerServiceBase(key) { public override Mock GetMock() { diff --git a/src/FrostFS.SDK.Tests/NetmapMock.cs b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs similarity index 78% rename from src/FrostFS.SDK.Tests/NetmapMock.cs rename to src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs index 0cc3889..07264ff 100644 --- a/src/FrostFS.SDK.Tests/NetmapMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs @@ -3,7 +3,7 @@ using FrostFS.Netmap; namespace FrostFS.SDK.Tests; -public class NetmapMock(string key) : ServiceBase(key) +public class NetmapMockFactory(string key) : ServiceBase(key) { public Mock GetMock() { diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs new file mode 100644 index 0000000..1d6c9e1 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -0,0 +1,101 @@ +using Moq; +using FrostFS.Object; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using FrostFS.Session; +using Google.Protobuf; +using System.Security.Cryptography; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.Tests; + +public class ObjectMockFactory(string key) : ObjectServiceBase(key) +{ + public override Mock GetMock() + { + var mock = new Mock(); + + GetResponse response = new() + { + Body = new GetResponse.Types.Body + { + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.Get( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncServerStreamingCall( + new AsyncStreamReaderMock(key, ObjectHeader), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } + + public ObjectHeader ObjectHeader { get; set; } +} + +public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader +{ + public GetResponse Current + { + get + { + var ecdsaKey = key.LoadWif(); + + var header = new Header + { + ContainerId = objectHeader.ContainerId.ToGrpcMessage(), + PayloadLength = objectHeader.PayloadLength, + Version = objectHeader.Version!.ToGrpcMessage(), + OwnerId = objectHeader.OwnerId!.ToGrpcMessage() + }; + + foreach (var attr in objectHeader.Attributes) + header.Attributes.Add(attr.ToGrpcMessage()); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Init = new GetResponse.Types.Body.Types.Init + { + Header = header, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, + Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(ecdsaKey.PublicKey()), + Sign = ByteString.CopyFrom(ecdsaKey.SignData(header.ToByteArray())), + } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return response; + } + } + + public Task MoveNext(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs new file mode 100644 index 0000000..83beaf8 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -0,0 +1,57 @@ +using Moq; +using FrostFS.Session; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using Google.Protobuf; + +namespace FrostFS.SDK.Tests; + +public class SessionMockFactory(string key) : ServiceBase(key) +{ + public byte[]? SessionId { get; set; } + + public byte[]? SessionKey { get; set; } + + public Mock GetMock() + { + var mock = new Mock(); + + Random rand = new(); + SessionId = new byte[32]; + SessionKey = new byte[32]; + + rand.NextBytes(SessionId); + rand.NextBytes(SessionKey); + + CreateResponse response = new() + { + Body = new CreateResponse.Types.Body + { + Id = ByteString.CopyFrom(SessionId), + SessionKey = ByteString.CopyFrom(SessionId) + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.CreateAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ObjectMock.cs b/src/FrostFS.SDK.Tests/ObjectMock.cs deleted file mode 100644 index 96296f8..0000000 --- a/src/FrostFS.SDK.Tests/ObjectMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Object; - -namespace FrostFS.SDK.Tests; - -public class ObjectMock(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -} diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs new file mode 100644 index 0000000..53c2a9a --- /dev/null +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -0,0 +1,66 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; + +using System.Security.Cryptography; + +namespace FrostFS.SDK.Tests; + +public class ObjectTest +{ + private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + [Fact] + public async void GetObjectTest() + { + var ecdsaKey = key.LoadWif(); + ContainerId cntId = new("xyz"); + + ObjectHeader header = new(cntId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) + { + PayloadLength = 1, + Version = new ModelsV2.Version(2, 13), + OwnerId = OwnerId.FromKey(ecdsaKey) + }; + + var objectMockFactory = new ObjectMockFactory(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid(), + ObjectHeader = header + }; + + var settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + var fsClient = Client.GetTestInstance( + settings, + null, + new NetmapMockFactory(this.key).GetMock().Object, + new SessionMockFactory(this.key).GetMock().Object, + new ContainerStub(this.key).GetMock().Object, + objectMockFactory.GetMock().Object); + + var objectId = fsClient.CalculateObjectId(header); + + var containerId = new ContainerId(Base58.Encode(objectMockFactory.ContainerGuid.ToBytes())); + + var result = await fsClient.GetObjectAsync(containerId, objectId); + + Assert.NotNull(result); + + Assert.Equal(header.ContainerId.Value, result.Header.ContainerId.Value); + Assert.Equal(header.OwnerId.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); + Assert.Equal(header.PayloadLength, result.Header.PayloadLength); + Assert.Single(result.Header.Attributes); + Assert.Equal(header.Attributes[0].Key, result.Header.Attributes[0].Key); + Assert.Equal(header.Attributes[0].Value,result.Header.Attributes[0].Value); + } +} diff --git a/src/FrostFS.SDK.Tests/SessionMock.cs b/src/FrostFS.SDK.Tests/SessionMock.cs deleted file mode 100644 index 8309fde..0000000 --- a/src/FrostFS.SDK.Tests/SessionMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Session; - -namespace FrostFS.SDK.Tests; - -public class SessionMock(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -} From fefa2da21849514098d213f658bd8014942e863c Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 4 Jul 2024 13:29:29 +0300 Subject: [PATCH 08/65] [#16] Unit tests Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 30 +- .../Interfaces/IFrostFSClient.cs | 4 +- .../Mappers/Object/Object.cs | 4 +- .../Mappers/Object/ObjectHeaderMapper.cs | 4 +- .../Mappers/Session/Session.cs | 2 - .../Services/ContainerServiceProvider.cs | 2 +- .../Services/NetmapServiceProvider.cs | 4 - .../Services/ObjectServiceProvider.cs | 40 +-- .../Services/ObjectTools.cs | 3 +- .../Tools/ClientEnvironment.cs | 1 - src/FrostFS.SDK.ClientV2/Tools/Object.cs | 12 +- .../Tools/RequestConstructor.cs | 1 + .../Tools/RequestSigner.cs | 10 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 1 + src/FrostFS.SDK.ModelsV2/CallStatistics.cs | 2 +- .../{ => Containers}/Container.cs | 1 - .../{ => Containers}/ContainerId.cs | 0 src/FrostFS.SDK.ModelsV2/Context.cs | 8 +- .../Object/{Object.cs => FrostFsObject.cs} | 10 +- .../Object/ObjectHeader.cs | 24 +- src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs | 12 +- .../Interfaces/IRequest.cs | 4 +- .../container/Extension.Message.cs | 1 + .../netmap/Extension.Message.cs | 3 +- .../object/Extension.Message.cs | 1 + .../session/Extension.Message.cs | 3 +- src/FrostFS.SDK.Tests/ContainerTest.cs | 150 +++++----- .../FrostFS.SDK.Tests.csproj | 10 + .../Mocks/AsyncStreamReaderMock.cs | 60 ++++ .../Mocks/ClientStreamWriter.cs | 29 ++ .../ContainerServiceBase.cs | 1 + ...ContainerMock copy.cs => ContainerStub.cs} | 1 - .../DeleteContainerMock.cs | 54 ---- .../ContainerServiceMocks/GetContainerMock.cs | 111 +++++++- .../ContainerServiceMocks/PutContainerMock.cs | 88 ------ .../ContainerServiceMocks/RequestData.cs | 12 + src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs | 14 - src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 78 ++++++ src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 257 +++++++++++++----- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 2 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 243 ++++++++++++++--- .../{ClientTestLive.cs => SmokeTests.cs} | 64 +++-- src/FrostFS.SDK.Tests/TestData/cat.jpg | Bin 0 -> 6740 bytes 43 files changed, 884 insertions(+), 477 deletions(-) rename src/FrostFS.SDK.ModelsV2/{ => Containers}/Container.cs (99%) rename src/FrostFS.SDK.ModelsV2/{ => Containers}/ContainerId.cs (100%) rename src/FrostFS.SDK.ModelsV2/Object/{Object.cs => FrostFsObject.cs} (82%) create mode 100644 src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs rename src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/{GetContainerMock copy.cs => ContainerStub.cs} (99%) delete mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs delete mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs delete mode 100644 src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs rename src/FrostFS.SDK.Tests/{ClientTestLive.cs => SmokeTests.cs} (85%) create mode 100644 src/FrostFS.SDK.Tests/TestData/cat.jpg diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index ca1acea..6e9cbd0 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Net.Http; -using System.Security.Cryptography; using System.Threading.Tasks; using Version = FrostFS.SDK.ModelsV2.Version; @@ -168,7 +167,7 @@ public class Client : IFrostFSClient return service.GetObjectHeadAsync(containerId, objectId, ctx!); } - public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) { var service = GetObjectService(ref ctx); return service.GetObjectAsync(containerId, objectId, ctx!); @@ -180,7 +179,7 @@ public class Client : IFrostFSClient return service.PutObjectAsync(putObjectParameters, ctx!); } - public Task PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default) + public Task PutSingleObjectAsync(FrostFsObject obj, Context? ctx = default) { var service = GetObjectService(ref ctx); return service.PutSingleObjectAsync(obj, ctx!); @@ -312,26 +311,21 @@ public class Client : IFrostFSClient private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) { - Uri uri; try { - uri = new Uri(host); + var uri = new Uri(host); + + if (channelOptions != null) + return GrpcChannel.ForAddress(uri, channelOptions); + + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions + { + HttpHandler = new HttpClientHandler() + }); } catch (UriFormatException e) { - var msg = $"Host '{host}' has invalid format. Error: {e.Message}"; - throw new ArgumentException(msg); + throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}"); } - - if (channelOptions != null) - { - return GrpcChannel.ForAddress(uri, channelOptions); - } - - - return GrpcChannel.ForAddress(uri, new GrpcChannelOptions - { - HttpHandler = new HttpClientHandler() - }); } } diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index df5bcce..bed4136 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -34,11 +34,11 @@ public interface IFrostFSClient : IDisposable #region Object Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); - Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default); - Task PutSingleObjectAsync(ModelsV2.Object obj, Context? context = default); + Task PutSingleObjectAsync(FrostFsObject obj, Context? context = default); Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs index be61b13..2804b65 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -4,9 +4,9 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectMapper { - public static ModelsV2.Object ToModel(this Object.Object obj) + public static FrostFsObject ToModel(this Object.Object obj) { - return new ModelsV2.Object( + return new FrostFsObject( ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), obj.Header.ToModel(), obj.Payload.ToByteArray()); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 6ded9d2..2292811 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -44,7 +44,7 @@ public static class ObjectHeaderMapper SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null }; - if (split.Children != null && split.Children.Any()) + if (split.Children != null && split.Children.Count != 0) head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); } @@ -81,7 +81,7 @@ public static class ObjectHeaderMapper Previous = header.Split.Previous?.ToModel() }; - if (header.Split.Children.Any()) + if (header.Split.Children.Count != 0) model.Split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs index 4b85759..e565891 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs @@ -1,5 +1,3 @@ - -using System; using Google.Protobuf; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index b2aadad..6a3036b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ModelsV2; using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using System.Collections.Generic; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 1060a20..441d13b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -51,10 +51,6 @@ internal class NetmapServiceProvider : ContextAccessor var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); - //var response = await Context.InvokeAsyncUnaryWithMetrics(() => - // netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken), - // nameof(netmapServiceClient.LocalNodeInfoAsync)); - Verifier.CheckResponse(response); return response.Body.ToModel(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 2439c6e..1937e70 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -43,7 +43,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return response.Body.Header.Header.ToModel(); } - internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); @@ -70,9 +70,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(Context.Key); - var obj = await GetObject(request, ctx); - - return obj; + return await GetObject(request, ctx); } internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) @@ -89,7 +87,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return PutStreamObject(parameters, ctx); } - internal async Task PutSingleObjectAsync(ModelsV2.Object modelObject, Context ctx) + internal async Task PutSingleObjectAsync(FrostFsObject modelObject, Context ctx) { var sessionToken = await GetOrCreateSession(ctx); @@ -178,7 +176,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C ObjectId? objectId; List sentObjectIds = []; - ModelsV2.Object? currentObject; + + FrostFsObject? currentObject; var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); @@ -199,7 +198,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C largeObject.AppendBlock(buffer, bytesCount); - currentObject = new ModelsV2.Object(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) + currentObject = new FrostFsObject(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) .SetSplit(split); if (largeObject.PayloadLength == fullLength) @@ -231,6 +230,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } currentObject.AddAttributes(parameters.Header!.Attributes); + return await PutSingleObjectAsync(currentObject, ctx); } @@ -247,7 +247,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var oid = new ObjectID { Value = hdr.Sha256() }; - var request = new PutRequest + var initRequest = new PutRequest { Body = new PutRequest.Types.Body { @@ -258,8 +258,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - request.AddMetaHeader(); - request.AddObjectSessionToken( + initRequest.AddMetaHeader(); + initRequest.AddObjectSessionToken( sessionToken, hdr.ContainerId, oid, @@ -267,9 +267,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Context.Key ); - request.Sign(Context.Key); + initRequest.Sign(Context.Key); - using var stream = await PutObjectInit(request, ctx); + using var stream = await PutObjectInit(initRequest, ctx); + var buffer = new byte[Constants.ObjectChunkSize]; while (true) @@ -279,14 +280,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C if (bufferLength == 0) break; - request.Body = new PutRequest.Types.Body + var chunkRequest = new PutRequest(initRequest) { - Chunk = ByteString.CopyFrom(buffer[..bufferLength]), + Body = new PutRequest.Types.Body + { + Chunk = ByteString.CopyFrom(buffer[..bufferLength]), + }, + VerifyHeader = null }; - request.VerifyHeader = null; - request.Sign(Context.Key); - await stream.Write(request); + chunkRequest.Sign(Context.Key); + await stream.Write(chunkRequest); } var response = await stream.Close(); @@ -295,7 +299,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); } - private async Task GetObject(GetRequest request, Context ctx) + private async Task GetObject(GetRequest request, Context ctx) { var reader = GetObjectInit(request, ctx); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs index 95ba626..35f68ae 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs @@ -19,7 +19,7 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal Object.Object CreateObject(ModelsV2.Object @object) + internal Object.Object CreateObject(FrostFsObject @object) { var grpcHeader = @object.Header.ToGrpcMessage(); @@ -100,5 +100,4 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) Sum = ByteString.CopyFrom(data.Sha256()) }; } - } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index b37e7fc..dae8ff2 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,4 +1,3 @@ -using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; using Grpc.Net.Client; using System; diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs index e7e59f1..0a0290e 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -7,31 +7,31 @@ namespace FrostFS.SDK.ClientV2.Extensions; public static class ObjectExtensions { - public static ModelsV2.Object SetPayloadLength(this ModelsV2.Object obj, ulong length) + public static FrostFsObject SetPayloadLength(this FrostFsObject obj, ulong length) { obj.Header.PayloadLength = length; return obj; } - public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, string key, string value) + public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value) { obj.AddAttribute(new ObjectAttribute(key, value)); return obj; } - public static ModelsV2.Object AddAttribute(this ModelsV2.Object obj, ObjectAttribute attribute) + public static FrostFsObject AddAttribute(this FrostFsObject obj, ObjectAttribute attribute) { obj.Header.Attributes.Add(attribute); return obj; } - public static ModelsV2.Object AddAttributes(this ModelsV2.Object obj, IEnumerable attributes) + public static FrostFsObject AddAttributes(this FrostFsObject obj, IEnumerable attributes) { obj.Header.Attributes.AddRange(attributes); return obj; } - public static ModelsV2.Object SetSplit(this ModelsV2.Object obj, Split split) + public static FrostFsObject SetSplit(this FrostFsObject obj, Split split) { obj.Header.Split = split; return obj; @@ -43,7 +43,7 @@ public static class ObjectExtensions return linkObject; } - public static ModelsV2.Object CalculateObjectId(this ModelsV2.Object obj) + public static FrostFsObject CalculateObjectId(this FrostFsObject obj) { if (obj.Payload == null) throw new MissingFieldException("Payload cannot be null"); diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index 6b1d4cf..6e2a5dc 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -2,6 +2,7 @@ using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index 6016ddf..d9fa633 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -12,6 +12,7 @@ using Org.BouncyCastle.Math; using FrostFS.Refs; using FrostFS.SDK.Cryptography; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.SDK.ClientV2; @@ -67,20 +68,21 @@ public static class RequestSigner { var hash = new byte[65]; hash[0] = 0x04; - key - .SignHash(SHA512.Create().ComputeHash(data)) - .CopyTo(hash, 1); + + key.SignHash(SHA512.Create().ComputeHash(data)).CopyTo(hash, 1); + return hash; } public static Signature SignMessagePart(this ECDsa key, IMessage? data) { - var data2Sign = data is null ? Array.Empty() : data.ToByteArray(); + var data2Sign = data is null ? [] : data.ToByteArray(); var sig = new Signature { Key = ByteString.CopyFrom(key.PublicKey()), Sign = ByteString.CopyFrom(key.SignData(data2Sign)), }; + return sig; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 5811ff1..6e65c02 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -13,6 +13,7 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs index e6424b6..a6cbcbf 100644 --- a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs +++ b/src/FrostFS.SDK.ModelsV2/CallStatistics.cs @@ -2,6 +2,6 @@ namespace FrostFS.SDK.ModelsV2; public class CallStatistics { - public string MethodName { get; set; } + public string? MethodName { get; set; } public long ElapsedMicroSeconds { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Container.cs b/src/FrostFS.SDK.ModelsV2/Containers/Container.cs similarity index 99% rename from src/FrostFS.SDK.ModelsV2/Container.cs rename to src/FrostFS.SDK.ModelsV2/Containers/Container.cs index 4533325..02543b3 100644 --- a/src/FrostFS.SDK.ModelsV2/Container.cs +++ b/src/FrostFS.SDK.ModelsV2/Containers/Container.cs @@ -1,5 +1,4 @@ using System; - using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; diff --git a/src/FrostFS.SDK.ModelsV2/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/ContainerId.cs rename to src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs index e486464..b1038ec 100644 --- a/src/FrostFS.SDK.ModelsV2/Context.cs +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -8,7 +8,7 @@ namespace FrostFS.SDK.ClientV2; public class Context() { - private List interceptors; + private List? interceptors; public CancellationToken CancellationToken { get; set; } = default; public TimeSpan Timeout { get; set; } = default; @@ -16,11 +16,11 @@ public class Context() public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; - public Action Callback { get; set; } + public Action? Callback { get; set; } public List Interceptors { - get { return interceptors ??= []; } - set { interceptors = value; } + get { return this.interceptors ??= []; } + set { this.interceptors = value; } } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/Object.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs similarity index 82% rename from src/FrostFS.SDK.ModelsV2/Object/Object.cs rename to src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index e129601..4cbfaae 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Object.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -4,16 +4,16 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class Object +public class FrostFsObject { - public Object(ObjectId objectId, ObjectHeader header, byte[] payload) + public FrostFsObject(ObjectId objectId, ObjectHeader header, byte[] payload) { ObjectId = objectId; Payload = payload; Header = header; } - public Object(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) + public FrostFsObject(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) { Payload = payload; Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); @@ -41,7 +41,7 @@ public class Object } } -public class LargeObject(ContainerId container) : Object(container, []) +public class LargeObject(ContainerId container) : FrostFsObject(container, []) { private readonly SHA256 payloadHash = SHA256.Create(); @@ -64,7 +64,7 @@ public class LargeObject(ContainerId container) : Object(container, []) } } -public class LinkObject : Object +public class LinkObject : FrostFsObject { public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) { diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index 83bee87..715f903 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -1,35 +1,27 @@ using System.Collections.Generic; - using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class ObjectHeader +public class ObjectHeader( + ContainerId containerId, + ObjectType type = ObjectType.Regular, + params ObjectAttribute[] attributes + ) { public OwnerId? OwnerId { get; set; } - public List Attributes { get; set; } + public List Attributes { get; set; } = [.. attributes]; - public ContainerId ContainerId { get; set; } + public ContainerId ContainerId { get; set; } = containerId; public ulong PayloadLength { get; set; } public byte[]? PayloadCheckSum { get; set; } - public ObjectType ObjectType { get; set; } + public ObjectType ObjectType { get; set; } = type; public Version? Version { get; set; } public Split? Split { get; set; } - - public ObjectHeader( - ContainerId containerId, - ObjectType type = ObjectType.Regular, - params ObjectAttribute[] attributes - ) - { - Attributes = [.. attributes]; - ContainerId = containerId; - ObjectType = type; - } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs index 2bcf3ab..74e6bfb 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs @@ -4,21 +4,15 @@ using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.ModelsV2; -public class ObjectId +public class ObjectId(string id) { - public string Value { get; } - - public ObjectId(string id) - { - Value = id; - } + public string Value { get; } = id; public static ObjectId FromHash(byte[] hash) { if (hash.Length != Constants.Sha256HashLength) - { throw new FormatException("ObjectID must be a sha256 hash."); - } + return new ObjectId(Base58.Encode(hash)); } diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs index 7efad42..42129c3 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs @@ -1,4 +1,6 @@ -namespace FrostFS.Session; +using FrostFS.Session; + +namespace FrostFS.SDK.ProtosV2.Interfaces; public interface IRequest : IVerifiableMessage { diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index d8bd601..5d48399 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -1,6 +1,7 @@ using Google.Protobuf; using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.Container; diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs index ba2d11e..2c1c8b6 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs @@ -1,4 +1,5 @@ -using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; using Google.Protobuf; namespace FrostFS.Netmap; diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index de933fb..dbfdd79 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs index 937af24..decf002 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs @@ -1,4 +1,5 @@ -using Google.Protobuf; +using FrostFS.SDK.ProtosV2.Interfaces; +using Google.Protobuf; namespace FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index b915304..d41edb8 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,118 +1,112 @@ using FrostFS.SDK.ClientV2; -using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; +using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ModelsV2.Enums; +using Google.Protobuf; +using FrostFS.SDK.ClientV2.Interfaces; namespace FrostFS.SDK.Tests; -public class ContainerTest -{ - private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - [Fact] - public async void CreateContainerTest() +public abstract class ContainerTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + protected ContainerMocker Mocker { get; set; } + + protected ContainerTestsBase() { - var factory = new PutContainerMockFactory(this.key) + Settings = Options.Create(new ClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + Mocker = new ContainerMocker(this.key) { PlacementPolicy = new PlacementPolicy(true, new Replica(1)), Version = new ModelsV2.Version(2, 13), ContainerGuid = Guid.NewGuid() }; + } - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + protected IFrostFSClient GetClient() + { + return ClientV2.Client.GetTestInstance( + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); - - var result = await fsClient.CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, factory.PlacementPolicy)); +public class ContainerTest : ContainerTestsBase +{ + [Fact] + public async void CreateContainerTest() + { + var result = await GetClient().CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); Assert.NotNull(result); Assert.NotNull(result.Value); - Assert.True(Base58.Encode(factory.ContainerGuid.ToBytes()) == result.Value); + Assert.True(Base58.Encode(Mocker.ContainerGuid.ToBytes()) == result.Value); } [Fact] public async void GetContainerTest() { - var factory = new GetContainerMockFactory(this.key) - { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), - Acl = BasicAcl.PublicRO, - ContainerGuid = Guid.NewGuid(), - }; + var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + Mocker.Acl = BasicAcl.PublicRO; - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); - - var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); - - var context = new Context(); - - var result = await fsClient.GetContainerAsync(cid, context); + var result = await GetClient().GetContainerAsync(cid); Assert.NotNull(result); - Assert.Equal(factory.Acl, result.BasicAcl); - Assert.Equal(factory.ContainerGuid, result.Nonce); - Assert.Equal(0, factory.PlacementPolicy.CompareTo(result.PlacementPolicy)); - Assert.Equal(factory.Version.ToString(), result.Version!.ToString()); + Assert.Equal(Mocker.Acl, result.BasicAcl); + Assert.Equal(Mocker.ContainerGuid, result.Nonce); + Assert.Equal(0, Mocker.PlacementPolicy.CompareTo(result.PlacementPolicy)); + Assert.Equal(Mocker.Version.ToString(), result.Version!.ToString()); + } + + [Fact] + public async void GetContainerListTest() + { + Mocker.ContainerIds.Add([0xaa]); + Mocker.ContainerIds.Add([0xbb]); + Mocker.ContainerIds.Add([0xcc]); + + var result = GetClient().ListContainersAsync(); + + Assert.NotNull(result); + + int i = 0; + await foreach (var cid in result) + { + var val = Base58.Encode(ByteString.CopyFrom(Mocker.ContainerIds[i++]).ToByteArray()); + Assert.Equal(val, cid.Value); + } + + Assert.Equal(3, i); } [Fact] public async void DeleteContainerAsyncTest() { - var factory = new DeleteContainerMockFactory(this.key) - { - Version = new ModelsV2.Version(2, 13), - Acl = BasicAcl.PublicRW, - ContainerGuid = Guid.NewGuid(), - }; + var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - var settings = Options.Create(new ClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); + await GetClient().DeleteContainerAsync(cid); - var fsClient = Client.GetTestInstance( - settings, - null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - factory.GetMock().Object, - new ObjectMockFactory(this.key).GetMock().Object); + Assert.Single(Mocker.Requests); - var cid = new ContainerId(Base58.Encode(factory.ContainerGuid.ToBytes())); - - await fsClient.DeleteContainerAsync(cid); - - Assert.Single(factory.Requests); - - var request = factory.Requests.First(); + var request = Mocker.Requests.First(); Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); } diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj index 931a072..2b74f0f 100644 --- a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -9,6 +9,10 @@ true + + + + @@ -25,4 +29,10 @@ + + + PreserveNewest + + + diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs new file mode 100644 index 0000000..45b17bc --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -0,0 +1,60 @@ +using FrostFS.Object; +using FrostFS.Session; +using Google.Protobuf; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using System.Security.Cryptography; + +namespace FrostFS.SDK.Tests; + +public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader +{ + public GetResponse Current + { + get + { + var header = new Header + { + ContainerId = objectHeader.ContainerId.ToGrpcMessage(), + PayloadLength = objectHeader.PayloadLength, + Version = objectHeader.Version!.ToGrpcMessage(), + OwnerId = objectHeader.OwnerId!.ToGrpcMessage() + }; + + foreach (var attr in objectHeader.Attributes) + header.Attributes.Add(attr.ToGrpcMessage()); + + var response = new GetResponse + { + Body = new GetResponse.Types.Body + { + Init = new GetResponse.Types.Body.Types.Init + { + Header = header, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, + Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Sign = ByteString.CopyFrom(Key.SignData(header.ToByteArray())), + } + } + }, + MetaHeader = new ResponseMetaHeader() + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return response; + } + } + + public Task MoveNext(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs new file mode 100644 index 0000000..fb1fe2d --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -0,0 +1,29 @@ +using Grpc.Core; +using FrostFS.SDK.ProtosV2.Interfaces; + +namespace FrostFS.SDK.Tests; + +public class ClientStreamWriter : IClientStreamWriter +{ + public List Messages { get; set; } = []; + public bool CompletedTask { get; private set; } + + public WriteOptions? WriteOptions + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public Task CompleteAsync() + { + CompletedTask = true; + return Task.CompletedTask; + } + + public Task WriteAsync(IRequest message) + { + Messages.Add(message); + return Task.CompletedTask; + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 0ea346d..530294a 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -17,6 +17,7 @@ namespace FrostFS.SDK.Tests; public abstract class ServiceBase(string key) { + public string StringKey { get; private set; } = key; public ECDsa Key { get; private set; } = key.LoadWif(); public ModelsV2.Version Version { get; set; } = DefaultVersion; public BasicAcl Acl { get; set; } = DefaultAcl; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs similarity index 99% rename from src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs rename to src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs index 3a17e88..c0b69a3 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock copy.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs @@ -1,7 +1,6 @@ using FrostFS.Container; using Moq; - namespace FrostFS.SDK.Tests; public class ContainerStub(string key) : ContainerServiceBase(key) diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs deleted file mode 100644 index ba03978..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/DeleteContainerMock.cs +++ /dev/null @@ -1,54 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Grpc.Core; -using Moq; - -namespace FrostFS.SDK.Tests; - -public class DeleteContainerMockFactory(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - var v = mock.Setup(x => x.DeleteAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Requests.Add(new RequestData(r, m, dt, ct)); - - var response = new DeleteResponse - { - Body = new DeleteResponse.Types.Body(), - MetaHeader = new ResponseMetaHeader() - }; - - var metadata = new Metadata(); - - response.VerifyHeader = GetResponseVerificationHeader(response); - - return new AsyncUnaryCall( - Task.FromResult(response), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - }); - - return mock; - } - - public List> Requests = []; -} - -public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) -{ - public T Request => request; - public Metadata Metadata => m; - public DateTime? deadline => dt; - public CancellationToken CancellationToken => ct; -} - \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 8b2a496..9a72f09 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -8,10 +8,13 @@ using FrostFS.SDK.ClientV2; using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.Session; +using FrostFS.Refs; +using System.Collections.Generic; namespace FrostFS.SDK.Tests; -public class GetContainerMockFactory(string key) : ContainerServiceBase(key) +public class ContainerMocker(string key) : ContainerServiceBase(key) { public override Mock GetMock() { @@ -19,7 +22,46 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) var grpcVersion = Version.ToGrpcMessage(); - var response = new GetResponse + PutResponse putResponse = new() + { + Body = new PutResponse.Types.Body + { + ContainerId = new ContainerID + { + Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) + } + }, + MetaHeader = new ResponseMetaHeader + { + Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), + Epoch = 100, + Ttl = 1 + } + }; + + putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse); + + var metadata = new Metadata(); + var putContainerResponse = new AsyncUnaryCall( + Task.FromResult(putResponse), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + + mock.Setup(x => x.PutAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return putContainerResponse; + }); + + var getResponse = new GetResponse { Body = new GetResponse.Types.Body { @@ -34,7 +76,7 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) MetaHeader = ResponseMetaHeader }; - response.VerifyHeader = GetResponseVerificationHeader(response); + getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); mock.Setup(x => x.GetAsync( It.IsAny(), @@ -46,13 +88,74 @@ public class GetContainerMockFactory(string key) : ContainerServiceBase(key) Verifier.CheckRequest(r); return new AsyncUnaryCall( - Task.FromResult(response), + Task.FromResult(getResponse), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); + var listResponse = new ListResponse + { + Body = new ListResponse.Types.Body(), + MetaHeader = ResponseMetaHeader + }; + + foreach (var item in ContainerIds) + { + listResponse.Body.ContainerIds.Add(new ContainerID { Value = ByteString.CopyFrom(item) }); + } + + listResponse.VerifyHeader = GetResponseVerificationHeader(listResponse); + + mock.Setup(x => x.ListAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ListRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(listResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + var v = mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Requests.Add(new RequestData(r, m, dt, ct)); + + var response = new DeleteResponse + { + Body = new DeleteResponse.Types.Body(), + MetaHeader = new ResponseMetaHeader() + }; + + var metadata = new Metadata(); + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(metadata), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => metadata, + () => { }); + }); + return mock; } + + public List ContainerIds { get; set; } = []; + + public List> Requests { get; set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs deleted file mode 100644 index c6abeab..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/PutContainerMock.cs +++ /dev/null @@ -1,88 +0,0 @@ -using FrostFS.Container; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; -using Moq; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.Cryptography; -using FrostFS.Refs; - -namespace FrostFS.SDK.Tests; - -public class PutContainerMockFactory(string key) : ContainerServiceBase(key) -{ - public override Mock GetMock() - { - var mock = new Mock(); - - PutResponse response = new() - { - Body = new PutResponse.Types.Body - { - ContainerId = new ContainerID - { - Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) - } - }, - MetaHeader = new ResponseMetaHeader - { - Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), - Epoch = 100, - Ttl = 1 - } - }; - - response.VerifyHeader = PutResponseVerificationHeader(response); - - var metadata = new Metadata(); - var putContainerResponse = new AsyncUnaryCall( - Task.FromResult(response), - Task.FromResult(metadata), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => metadata, - () => { }); - - mock.Setup(x => x.PutAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((PutRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Verifier.CheckRequest(r); - - return putContainerResponse; - }); - - return mock; - } - - private ResponseVerificationHeader PutResponseVerificationHeader(PutResponse response) - { - var verifyHeader = new ResponseVerificationHeader - { - MetaSignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) - }, - BodySignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) - }, - OriginSignature = new Signature - { - Key = ByteString.CopyFrom(Key.PublicKey()), - Scheme = SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData([])) - } - }; - - return verifyHeader; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs new file mode 100644 index 0000000..8af698b --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs @@ -0,0 +1,12 @@ +using Grpc.Core; + +namespace FrostFS.SDK.Tests; + +public class RequestData(T request, Metadata m, DateTime? dt, CancellationToken ct) +{ + public T Request => request; + public Metadata Metadata => m; + public DateTime? Deadline => dt; + public CancellationToken CancellationToken => ct; +} + \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs b/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs deleted file mode 100644 index 07264ff..0000000 --- a/src/FrostFS.SDK.Tests/Mocks/NetmapMock.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moq; -using FrostFS.Netmap; - -namespace FrostFS.SDK.Tests; - -public class NetmapMockFactory(string key) : ServiceBase(key) -{ - public Mock GetMock() - { - var mock = new Mock(); - - return mock; - } -} diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs new file mode 100644 index 0000000..6cef45f --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -0,0 +1,78 @@ +using Moq; +using FrostFS.Netmap; +using Grpc.Core; +using FrostFS.SDK.ClientV2; +using Google.Protobuf; +using NuGet.Frameworks; + +namespace FrostFS.SDK.Tests; + +public class NetworkMocker(string key) : ServiceBase(key) +{ + private static readonly string[] parameterKeys = [ + "ContainerFee", + "EpochDuration", + "IRCandidateFee", + "MaxECDataCount", + "MaxECParityCount", + "MaxObjectSize", + "WithdrawalFee", + "HomomorphicHashingDisabled", + "MaintenanceModeAllowed" ]; + + public Dictionary? Parameters { get; set; } + + public Mock GetMock() + { + var mock = new Mock(); + + var networkInfoResponse = new NetworkInfoResponse(); + + var networkConfig = new NetworkConfig(); + + foreach (var key in parameterKeys) + { + networkConfig.Parameters.Add(new NetworkConfig.Types.Parameter + { + Key = ByteString.CopyFromUtf8(key), + Value = (Parameters != null && Parameters.TryGetValue(key, out byte[]? value)) ? ByteString.CopyFrom(value) : ByteString.CopyFrom(0) + }); + } + + var response = new NetworkInfoResponse + { + Body = new NetworkInfoResponse.Types.Body + { + NetworkInfo = new NetworkInfo + { + CurrentEpoch = 99, + MagicNumber = 13, + MsPerBlock = 999, + NetworkConfig = networkConfig + } + }, + MetaHeader = ResponseMetaHeader + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + mock.Setup(x => x.NetworkInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((NetworkInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + return mock; + } +} diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 1d6c9e1..db4df1e 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -1,101 +1,208 @@ -using Moq; -using FrostFS.Object; -using Grpc.Core; -using FrostFS.SDK.ClientV2; -using FrostFS.Session; using Google.Protobuf; +using Grpc.Core; +using Moq; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ModelsV2; +using FrostFS.Object; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using System.Security.Cryptography; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.Tests; -public class ObjectMockFactory(string key) : ObjectServiceBase(key) +public class ObjectMocker(string key) : ObjectServiceBase(key) { public override Mock GetMock() { var mock = new Mock(); - GetResponse response = new() + if (ObjectHeader != null) { - Body = new GetResponse.Types.Body - { - }, - MetaHeader = ResponseMetaHeader - }; - - response.VerifyHeader = GetResponseVerificationHeader(response); - - mock.Setup(x => x.Get( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + mock.Setup(x => x.Get( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRequest r, Metadata m, DateTime? dt, CancellationToken ct) => { Verifier.CheckRequest(r); - return new AsyncServerStreamingCall( - new AsyncStreamReaderMock(key, ObjectHeader), + return new AsyncServerStreamingCall( + new AsyncStreamReaderMock(StringKey, ObjectHeader), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, () => { }); }); + HeadResponse ??= new Header + { + CreationEpoch = 99, + ContainerId = ObjectHeader.ContainerId.ToGrpcMessage(), + ObjectType = ObjectType.Regular, + OwnerId = ObjectHeader.OwnerId!.ToGrpcMessage(), + PayloadLength = 1, + PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) }, + Version = ObjectHeader.Version!.ToGrpcMessage() + }; + + HeadResponse headResponse = new() + { + Body = new HeadResponse.Types.Body + { + Header = new HeaderWithSignature + { + Header = HeadResponse + } + }, + MetaHeader = ResponseMetaHeader + }; + + headResponse.Body.Header.Header.Attributes.Add(new Header.Types.Attribute { Key = "k", Value = "v" }); + + headResponse.Body.Header.Signature = new Refs.Signature + { + Key = ByteString.CopyFrom(Key.PublicKey()), + Sign = ByteString.CopyFrom(Key.SignData(headResponse.Body.Header.ToByteArray())), + }; + + headResponse.VerifyHeader = GetResponseVerificationHeader(headResponse); + + mock.Setup(x => x.HeadAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((HeadRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + HeadRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(headResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + } + + if (ResultObjectId != null) + { + PutResponse putResponse = new() + { + Body = new PutResponse.Types.Body + { + ObjectId = new Refs.ObjectID + { + Value = ByteString.CopyFrom(ResultObjectId) + } + }, + MetaHeader = ResponseMetaHeader, + }; + + putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse); + + mock.Setup(x => x.Put( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((Metadata m, DateTime? dt, CancellationToken ct) => + { + return new AsyncClientStreamingCall( + ClientStreamWriter!, + Task.FromResult(putResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + + PutSingleResponse putSingleResponse = new() + { + Body = new PutSingleResponse.Types.Body(), + MetaHeader = ResponseMetaHeader, + }; + + putSingleResponse.VerifyHeader = GetResponseVerificationHeader(putSingleResponse); + + mock.Setup(x => x.PutSingleAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((PutSingleRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + PutSingleRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(putSingleResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + if (ObjectId != null) + { + DeleteResponse deleteResponse = new() + { + Body = new DeleteResponse.Types.Body + { + Tombstone = new Refs.Address + { + ContainerId = ObjectHeader!.ContainerId.ToGrpcMessage(), + ObjectId = ObjectId.ToGrpcMessage() + } + }, + MetaHeader = ResponseMetaHeader + }; + + deleteResponse.VerifyHeader = GetResponseVerificationHeader(deleteResponse); + + mock.Setup(x => x.DeleteAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((DeleteRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + DeleteRequests.Add(r); + + return new AsyncUnaryCall( + Task.FromResult(deleteResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + + return mock; } - public ObjectHeader ObjectHeader { get; set; } -} - -public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader -{ - public GetResponse Current - { - get - { - var ecdsaKey = key.LoadWif(); - - var header = new Header - { - ContainerId = objectHeader.ContainerId.ToGrpcMessage(), - PayloadLength = objectHeader.PayloadLength, - Version = objectHeader.Version!.ToGrpcMessage(), - OwnerId = objectHeader.OwnerId!.ToGrpcMessage() - }; - - foreach (var attr in objectHeader.Attributes) - header.Attributes.Add(attr.ToGrpcMessage()); - - var response = new GetResponse - { - Body = new GetResponse.Types.Body - { - Init = new GetResponse.Types.Body.Types.Init - { - Header = header, - ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, - Signature = new Refs.Signature - { - Key = ByteString.CopyFrom(ecdsaKey.PublicKey()), - Sign = ByteString.CopyFrom(ecdsaKey.SignData(header.ToByteArray())), - } - } - }, - MetaHeader = new ResponseMetaHeader() - }; - - response.VerifyHeader = GetResponseVerificationHeader(response); - - return response; - } - } - - public Task MoveNext(CancellationToken cancellationToken) - { - return Task.FromResult(true); - } + public ObjectId? ObjectId { get; set; } + + public ObjectHeader? ObjectHeader { get; set; } + + public Header? HeadResponse { get; set; } + + public byte[]? ResultObjectId { get; set; } + + public ClientStreamWriter? ClientStreamWriter { get; private set; } = new (); + + public List PutSingleRequests { get; private set; } = []; + + public List DeleteRequests { get; private set; } = []; + + public List HeadRequests { get; private set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index 83beaf8..094090d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -6,7 +6,7 @@ using Google.Protobuf; namespace FrostFS.SDK.Tests; -public class SessionMockFactory(string key) : ServiceBase(key) +public class SessionMocker(string key) : ServiceBase(key) { public byte[]? SessionId { get; set; } diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 53c2a9a..399dc07 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -1,66 +1,237 @@ +using FrostFS.Refs; using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using Google.Protobuf; using Microsoft.Extensions.Options; - using System.Security.Cryptography; +using System.Text; namespace FrostFS.SDK.Tests; -public class ObjectTest +public abstract class ObjectTestsBase { - private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - [Fact] - public async void GetObjectTest() + protected IOptions Settings { get; set; } + protected ContainerId ContainerId { get; set; } + + protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); + protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key); + protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key); + protected ObjectMocker Mocker { get; set; } + + protected ObjectTestsBase() { var ecdsaKey = key.LoadWif(); - ContainerId cntId = new("xyz"); - - ObjectHeader header = new(cntId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) - { - PayloadLength = 1, - Version = new ModelsV2.Version(2, 13), - OwnerId = OwnerId.FromKey(ecdsaKey) - }; - - var objectMockFactory = new ObjectMockFactory(this.key) - { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), - ContainerGuid = Guid.NewGuid(), - ObjectHeader = header - }; - var settings = Options.Create(new ClientSettings + Settings = Options.Create(new ClientSettings { Key = key, Host = "http://localhost:8080" }); - var fsClient = Client.GetTestInstance( - settings, + Mocker = new ObjectMocker(key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13), + ContainerGuid = Guid.NewGuid() + }; + + ContainerId = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + + Mocker.ObjectHeader = new(ContainerId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) + { + PayloadLength = 1, + Version = new ModelsV2.Version(2, 13), + OwnerId = OwnerId.FromKey(ecdsaKey) + }; + } + + protected IFrostFSClient GetClient() + { + return Client.GetTestInstance( + Settings, null, - new NetmapMockFactory(this.key).GetMock().Object, - new SessionMockFactory(this.key).GetMock().Object, - new ContainerStub(this.key).GetMock().Object, - objectMockFactory.GetMock().Object); + NetworkMocker.GetMock().Object, + SessionMocker.GetMock().Object, + ContainerMocker.GetMock().Object, + Mocker.GetMock().Object); + } +} - var objectId = fsClient.CalculateObjectId(header); +public class ObjectTest : ObjectTestsBase +{ + [Fact] + public async void GetObjectTest() + { + var client = GetClient(); + var objectId = client.CalculateObjectId(Mocker.ObjectHeader!); - var containerId = new ContainerId(Base58.Encode(objectMockFactory.ContainerGuid.ToBytes())); + var context = new Context + { + Timeout = TimeSpan.FromSeconds(2) + }; - var result = await fsClient.GetObjectAsync(containerId, objectId); + var result = await client.GetObjectAsync(ContainerId, objectId, context); Assert.NotNull(result); - Assert.Equal(header.ContainerId.Value, result.Header.ContainerId.Value); - Assert.Equal(header.OwnerId.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); - Assert.Equal(header.PayloadLength, result.Header.PayloadLength); + Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value); + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); Assert.Single(result.Header.Attributes); - Assert.Equal(header.Attributes[0].Key, result.Header.Attributes[0].Key); - Assert.Equal(header.Attributes[0].Value,result.Header.Attributes[0].Value); + Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); + Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value,result.Header.Attributes[0].Value); + } + + [Fact] + public async void PutObjectTest() + { + Mocker.ResultObjectId = SHA256.HashData([]); + + Random rnd = new(); + var bytes = new byte[1024]; + rnd.NextBytes(bytes); + + var param = new PutObjectParameters + { + Header = Mocker.ObjectHeader, + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var result = await GetClient().PutObjectAsync(param); + + var sentMessages = Mocker.ClientStreamWriter!.Messages; + + var body1 = sentMessages.ElementAt(0).GetBody() as Object.PutRequest.Types.Body; + var body2 = sentMessages.ElementAt(1).GetBody() as Object.PutRequest.Types.Body; + + Assert.NotNull(result); + Assert.Equal(Mocker.ResultObjectId, result.ToHash()); + + Assert.True(Mocker.ClientStreamWriter.CompletedTask); + + Assert.Equal(0, body1!.Chunk.Length); + Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Init, body1!.ObjectPartCase); + + Assert.Equal(1024, body2!.Chunk.Length); + Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Chunk, body2!.ObjectPartCase); + } + + [Fact] + public async void ClientCutTest() + { + NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } }; + + var blockSize = 2560; + byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); + var fileLength = bytes.Length; + + var param = new PutObjectParameters + { + Header = Mocker.ObjectHeader, + Payload = new MemoryStream(bytes), + ClientCut = true + }; + + var result = await GetClient().PutObjectAsync(param); + + var sentMessages = Mocker.PutSingleRequests.ToArray(); + + Assert.Equal(4, sentMessages.Length); + + var object_0 = sentMessages[0].Body.Object; + var object_1 = sentMessages[1].Body.Object; + var object_2 = sentMessages[2].Body.Object; + var object_3 = sentMessages[3].Body.Object; + + Assert.NotNull(object_0.Header.Split.SplitId); + Assert.Null(object_0.Header.Split.Previous); + Assert.Equal(blockSize, (int)object_0.Header.PayloadLength); + Assert.Equal(bytes[..blockSize], object_0.Payload); + Assert.True(object_0.Header.Attributes.Count == 0); + + Assert.Equal(object_0.Header.Split.SplitId, object_1.Header.Split.SplitId); + Assert.Equal(object_0.ObjectId, object_1.Header.Split.Previous); + Assert.Equal(blockSize, (int)object_1.Header.PayloadLength); + Assert.Equal(bytes[blockSize..(blockSize * 2)], object_1.Payload); + Assert.True(object_1.Header.Attributes.Count == 0); + + // last part + Assert.NotNull(object_2.Header.Split.Parent); + Assert.NotNull(object_2.Header.Split.ParentHeader); + Assert.NotNull(object_2.Header.Split.ParentSignature); + Assert.Equal(object_1.Header.Split.SplitId, object_2.Header.Split.SplitId); + Assert.Equal(object_1.ObjectId, object_2.Header.Split.Previous); + Assert.Equal(fileLength%blockSize, (int)object_2.Header.PayloadLength); + Assert.Equal(bytes[((fileLength/blockSize) * blockSize)..fileLength], object_2.Payload); + Assert.True(object_2.Header.Attributes.Count == 0); + + // link object + Assert.Equal(object_2.Header.Split.Parent, object_3.Header.Split.Parent); + Assert.Equal(object_2.Header.Split.ParentHeader, object_3.Header.Split.ParentHeader); + Assert.Equal(object_2.Header.Split.SplitId, object_3.Header.Split.SplitId); + Assert.Equal(0, (int)object_3.Header.PayloadLength); + Assert.Contains(object_0.ObjectId, object_3.Header.Split.Children); + Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); + Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); + Assert.True(object_2.Header.Attributes.Count == 0); + + Assert.Single(object_3.Header.Split.ParentHeader.Attributes); + Assert.Equal("k", object_3.Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", object_3.Header.Split.ParentHeader.Attributes[0].Value); + + var modelObjId = ObjectId.FromHash(object_3.Header.Split.Parent.Value.ToByteArray()); + + Assert.Equal(result.Value, modelObjId.ToString()); + } + + [Fact] + public async void DeleteObject() + { + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + await GetClient().DeleteObjectAsync(ContainerId, Mocker.ObjectId); + + var request = Mocker.DeleteRequests.FirstOrDefault(); + Assert.NotNull(request); + Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); + Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + } + + [Fact] + public async void GetHeaderTest() + { + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + var response = await GetClient().GetObjectHeadAsync(ContainerId, Mocker.ObjectId); + + var request = Mocker.HeadRequests.FirstOrDefault(); + Assert.NotNull(request); + Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); + Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + + Assert.NotNull(response); + Assert.Equal(ContainerId.Value, response.ContainerId.Value); + + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), response.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); + + Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); + + Assert.Equal(ObjectType.Regular, response.ObjectType); + + Assert.Single(response.Attributes); + + Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key); + Assert.Equal(Mocker.HeadResponse.Attributes[0].Value, response.Attributes.First().Value); + + Assert.Null(response.Split); } } diff --git a/src/FrostFS.SDK.Tests/ClientTestLive.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs similarity index 85% rename from src/FrostFS.SDK.Tests/ClientTestLive.cs rename to src/FrostFS.SDK.Tests/SmokeTests.cs index bfd50ca..6a33209 100644 --- a/src/FrostFS.SDK.Tests/ClientTestLive.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -5,14 +5,13 @@ using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; using Grpc.Core; -using Grpc.Net.Client; using Microsoft.Extensions.Options; using Grpc.Core.Interceptors; using System.Diagnostics; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.SmokeTests; -public class ClientTestLive +public class SmokeTests { private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; private readonly string url = "http://172.29.238.97:8080"; @@ -46,11 +45,24 @@ public class ClientTestLive Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); - Assert.True(result.PublicKey.Length > 0); + Assert.Equal(33, result.PublicKey.Length); Assert.Single(result.Addresses); Assert.Equal(9, result.Attributes.Count); } + [Fact] + public async void NodeInfo_Statictics_Test() + { + var ctx = new Context + { + Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") + }; + + using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + + var result = await fsClient.GetNodeInfoAsync(); + } + [Fact] public async void SimpleScenarioTest() { @@ -76,9 +88,7 @@ public class ClientTestLive Assert.NotNull(container); - Random rnd = new(); - var bytes = new byte[6 * 1024 * 1024 + 100]; - rnd.NextBytes(bytes); + var bytes = GetRandomBytes(6 * 1024 * 1024 + 100); var param = new PutObjectParameters { @@ -141,25 +151,21 @@ public class ClientTestLive await Cleanup(fsClient); - var containerId = await fsClient.CreateContainerAsync( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + var cnt = new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))); - var context = new Context - { - Timeout = TimeSpan.FromSeconds(10) + var containerId = await fsClient.CreateContainerAsync(cnt); + + var context = new Context + { + Timeout = TimeSpan.FromSeconds(10), + Interceptors = new([new MetricsInterceptor()]) }; - var metrics = new MetricsInterceptor(); - - context.Interceptors.Add(metrics); - var container = await GetContainer(fsClient, containerId, context); Assert.NotNull(container); - Random rnd = new(); - var bytes = new byte[6 * 1024 * 1024 + 100]; - rnd.NextBytes(bytes); + byte[] bytes = GetRandomBytes(150 * 1024 * 1024); var param = new PutObjectParameters { @@ -200,6 +206,8 @@ public class ClientTestLive ms.Write(chunk); } + Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + await Cleanup(fsClient); await Task.Delay(2000); @@ -210,15 +218,21 @@ public class ClientTestLive } } + private static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + private static IOptions GetOptions(string key, string url) { - var settings = new ClientSettings + return Options.Create(new ClientSettings { Key = key, Host = url - }; - - return Options.Create(settings); + }); } static async Task Cleanup(IFrostFSClient fsClient) @@ -243,7 +257,7 @@ public class ClientTestLive if (DateTime.UtcNow >= ctx.Deadline) throw new TimeoutException(); } - catch (Grpc.Core.RpcException) + catch (RpcException) { throw; } @@ -268,7 +282,7 @@ public class MetricsInterceptor() : Interceptor call.Dispose); } - private async Task HandleUnaryResponse(AsyncUnaryCall call) + private static async Task HandleUnaryResponse(AsyncUnaryCall call) { var watch = new Stopwatch(); watch.Start(); diff --git a/src/FrostFS.SDK.Tests/TestData/cat.jpg b/src/FrostFS.SDK.Tests/TestData/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03236d57c0bd3698dab8ba686ec867c0782de91a GIT binary patch literal 6740 zcmb7HbyQT}x1S*g1Y~Fd=^8qQ?hfgc6cA+q=?+mwx?voUmS!j=9T*U45W%5wK$MVf z5K-#k@B4l))_VWEbMIYypB+ZJo|di_00;yCfHx1|dL9rBAR!{A zqNbvyrlO{$qot?6dFdHwY5x*hhMSL;o|f@09~%QB_gz|Ab}@Eth=8!LFaxWEjD(;x zpOCNskd%~^f}Dbpl9Ew~m5p2I|J|-X0Kg!?J0LYakOP1R2I7N(*L?tH002PzH`4!Z zc=!Z_M8rVQjn;?~0K~(`Cn6@HAS3`1-q-*EH)=2;yBHCbs)o699BOL*&>vpXG)W_=rtZweEpbSz5m4HDbOU$?2*3ya@8NC+ARxR^v4U^p zczFL)4a5WEi&3$wnotv5;{as%H+nEW7@!Qm{4yWmJAEQ^J3EeiO<-YygP0QE)p~-9zE*@>10GxD(f^|JaF>hH)YE5Pg-M~G z&bk_%{>zPDzSi&z68Xev!MX!cjTce~KMSuN`$wXwI8zxx(`o(2bQ zej^kbO*&5~8w^TV`!{x|m>gbQetta(3_AIBIkHRo?^N8A#^mouA=mmZ`%|f6Og83k zVK9biJ0Fj4;(j$0xjpIMbinPbn(e_M+dT%0XO_6XZf}*5q}FZm8NGj;qs+*=sc{b* z)=faEBi0pAIa;pt^GfYQ?vW_B_xnwwJiVve7ctM-C^qV4GUMSOKs!Acf!_e$*wqNr z>m-GP*xBGD(*=CaeI7&zy{#D&uT+CSlBy4KGAH_*fvcB-TP&)2eth?=xQi$%?udBP zgM6bB$NW~TLK>|oO*uY>3EGOSs6Crx9lvN4RTbW}A}67u-KA%e_YDwcgWs=x;g=a| zVyW7ur(r`7SmsUxfbvI1*wyU!^V50oz)bE1W2=Lp%{r;2!g#-AF0xWEK?3Fwt#`Jq z2)%+7`l=BGvqdUh&gkg#VxCQcw{`JIsL@0`Dsg=w_;+7X3iU||o1!Be#P&aCiS8P(_wsjh?t*zx5R{TUgbS4M zS|Haz)ZiRfP?#l+T~Jk!i~c%(H&&%DWvfbUV`Vc=#ET71`sF57&q@C#@6*4M$M4+s z>+Y35?~2AHU;OR+Y;X{n_gCIyH>1vH>2F~~KReFLt~?>X-W<{07XFC5kMxtTw!m-% z@aKQxr*$7fgcxo1&){79YcnT2IpkBGSk^>}WVl#j%dEJw-Nl_(#$UDDnwI_C91d~G zCG>0LI<|#-I9m50*{vCIj`Xb3!h5BaN#xqxj(8!4K1_s0x};&IUIZctQi;<{!caa~ zF{$MPWmR_9Sevs+NYhZ8${j)fGIg{;74xN1E+{STbSbxihjO0XtDw`zH&B@}=RwifUX-1k*c3Mmu zkO|XtqS3oNTY)G1Idgf*ydnUpC0|K`tx1F7m4ZkZl+i7r{_D`1&C5AEqt_noD0-=P z0If>xZGHYHZIdH7WPb4w^8|8KT<7uqm2NkaAuQq~V_oYVhZB`qp- zYP|idpUgY!Ujy<6uK_d~#ML+Df=GG~@t;MYGeMxxa`N9@>qyORLQ8J_I*d%Sh0;(* zsQHY=J>cyORhMwI9V#D|6<@2QnPIzowk`yJVoA?2?1wC3G|4UDr!)&vi1m+R7jIlm zT6ja}@)|}jZ0KbWmU4C=f=2{HbMqm&X=HudU+17gP1U}Wga~r{;U&#iI^HZ^q(~{2 zN&vRa`Y=-@-v(o??)`KCR&j#gR;rO}HSB7>LnwkSL=kyE(_aHr?^bd zWdE8pp>27uY49-so9nwBnoq4`_mn4xFeo`2)7;{eoz& z(&F3VpV8_?WWUdEuGDj|C~NriN41_w)5vkvRKnDPMT$9KCusG^(4tO=R~^)|T~(AA z6rCcZBkk~Po5D1AZb$gT(L{TQ;WmDQ?w~ZCJM5drESF=3-FFy(`TevCTwf-eDHZlT zrE6IVSeabXIcugXDOdjiSn&t-yUp&{o*5I@uxjGFdP`H69B0s*G_S^|?P`3GM&MwG zu&t7W>94KbiZ7q)a-2}aP*-6+qM*G&h2?kUE6b|G zd2`XC@`-b`PgrX(fAD!nQR+2dJP{t2KtQSTuy9lPpB*&EMb^f<-Dqv1DC`=*{^&Q= z_Vlua)5pofh(uJT(b7?kk_BdY`W|<|VLmF)JTX!r!_~63KEiWbo%PUqkDih@5LJq<$+Zo^7HQ;=d!C%GSZr&}_leajB-lkKR9PZyaF~gjnOG~i80?iw z)n{p`QWYNLs)k@9chQ~i7X7szhU6X#jO&x62S`_lYl&)ZEW0fBe8j&v>-m<9D^Yvw z_6A`PB5Z4ErA=RJhkavU_+659m&|5_^MUh0Gn#H@cG7wAC zQ+HEL4wxGZN$jG{S{xl3s<2L%5!KV8AgDG;#$DSuHq@7H&ur0BwvXI$+`LBK z<*_HlI(Vo?Nmfo!R%lmzb52-+@_4{wh|tU$qgcPBbZPM6iTC`RC^C*>S>MKi{pPYM`xt6^y!5{iDElxsL4Y~oMp7H$wRJkmTDU5F&t_orFt z)um4_DXq^JpC>D(qL(ny*Bm=*o|J~wU4GR5&m|a-YTNp@P%z!HdJ%k;tkF9)#0FPQ z4gdEhXO%JVv--b!Mwy144&0W^sNnwUEd8S7AC?gnH*CG(4XcmqppP&%ZeTyCE^HNI zufTr1IX_!IyQ?5`>GK+>_t=6-(x+WJI~H1b&>P5e05Dp5%OAai>H=!iS^)%hv55oU z64jjTFvQ`$9P#WS33kiEr$2Q1U~V4*LMOl6al~r*5+}Y7;NcGV)!lbrMWIEf`%MfB z^^)fII8rOkKzOyRN#-Qj|DK9JPC`A3KoR2>qDY_5Uuh@d%{jKH4!?WEdqQnr*zFM~ z)L$6k&n-1L6bk=-_Ph3>VIT=AP!wZ1QEM4ftj4ZZH&xRHNqtSdu`K3!D`mxikj9n^ zz*8xMNEH7uqV$xT}EFeI4mKbEyH_j7g^xeb1hTh{F$S66GD zmofv@N>5;P8Ik-G$av*9G9hD}p8d=@t|teBtkItL45HHk%lLIxYlXH}q-mb!H%@?s{cK(MzfW5Y zJNfA90;+Jnf=%3yJ5E(KUP6vJr+$nrP`p*p=wKvm5QaDgV-(R-5qVmrV=UES>rne#S>0n3 zI}XFdtp&p)>I6EuwgtA3VXf$0HfevAb;>d(8Mg!_Vc#uGxp)OaA}SkA1)aJE;2d3l zJV+vsaF(-CQ!EA#>Z=Z%_8#Tueh?#RZ@vb6bq$b|P3QD)pDSqFFGHiM)S1gq-q*-f`_I%~$b70cK7YedOpa{D<~=8q9Y* zNJc{w7^eR)5{}kGO}(OjkwP0^f4c^R(KgPe#|~rzd`^o=OMXkyQ!#I?#26Vu)>E?Q zNrLhl9<$86^5&F&SQw^)$x5gz7P(EiuI%&)5LlR=H>4TP%x|x^FxmZv8-gwOBz8^8 zKizp^2~fTb2&fs@b1(UW(GWM52YC_SCtRuh$;IOm01CXIQFI8<-Ig4<_+9$0gFa<9 zUM-nQkm&5wh4FIp-X}%(CleUKbIGO3OC@Cug}_W2e=ZJbkQZz8&uy2W2G2MaGx&2<`dplq`yRU29_s45QfMhul+-sWgyWTI@0$edBP6VTXgl`zdxFkV~m*e!#Z=G0WOy!wITv-V`9xl$i)X*ych z@$ZPM(5!yIsr_e;MWQ2)AN2&p22cMi-*Ep~zgo8Uc=*`q_+!x{_0C1Q`K;7Zr?asw z3s4El8eMCB5o%*+3INnMNl7@o(j4wC_FEq;{L1hn5zU+(`-iuQ?qOar*CPsKO!l_5 z<7*54851fpPV>!l5{R3v%u8V|qQxKxK4IN&s{*wHS)c7?gVzgu^RWe9D{`&8^kl(d z7JY4W0u3|(xAy(M9a+A-oL62n{!OV9DvREVpz(e!|9FqU-?>D*b5&;uwdNNThnHT( z&{(C-Bx@|@vs!0Og(ZEsGtc^8k{)| ziB__;Sy^^KF2VL$RzxgxVkwIDggoKXNZ&m*b{Vu>o-oG39Qw%)YZpuw0&mQ*1V5#C zudHIg!&UsUL#ZgUeyBb~l0dp_`<`x(C`kbG+RTw;LA7V=Q^KCr87MUzg-rZb{G)F1 zVq)k6G?^TB+pCZDvCMZ5dnKH?PMo%RQgdZWiaxVjb&ZjT@Z2|hZf+L|5omq!#>6Wd zf1fm>wwB3zH8qyM2ylWcNp5B)JGZsf;Dsp#Hun^mqwDOx4WsEnx zvHl)?2DWX(JUD;yLAmyVXX+gvj&Z%W{wB^1tDx$MOeLg4UcU9ZXp=ySYkH~=>Llz7s?TpX@*9Uhj zWq!+o2iitAPdLa5lSMx%RP4p1ERP&kp7h&9pav2`$l?iSA+S@ynRv%t3h%9H&ognoWP=9Ot{npm<=~P~PhHBl@HuI-Bj<}4H?1AT}66)?VDbv-M zA(sW!BOLKEG0N(o{L;DN>=4xxx=PZ->mU&NdZ2n8w%TWfzxb8xF6|hbA0ag>R|(Ph z7zMNgn{z!buZ-ife$U z+u4sZ+vqwbGeedlxrA%Ln3>VLSCtvHGONZicZ22AXA)iZ#lGYQ>}i0nXU zVd-!zQ53(2dRi<0l0Z7b(YTf2BJ#Nz`pYfxEy^=hoOdWuQ5>z3EbB}2*Lub?+2{6s z8rGn#lQPk?@dKy8u+J#p=yj3v4qGDfipxhi(^-V<^}2;*?ewb0tPw(?JhwLA zT2-S$Wh-0P@uXzHGx-brZM})p(wSr4hdyp6X_-Gv5GfX}!_-&xx0CnTKTb>=s}dvt zkzG8jOzp_P>h3IxQl~>q6@Pc+)KUR-TSY11ZP=b4a42lsndd_c77sX-2#$97%D^`A zYs&HDx&0^~;rSMkG@}Y$npquik_L8C&OSJk!B-C={(8a@pD6j6-M7K8nV{E>UWZf` zIrEZWTSYE_?h=gV2+n#+w?{&@a*|CFJfR{-AS~N0!L_ZywXL5gA4$QCOVrqn=}0{BRJLhr{f;(ZGlF8-^cTty#L{>30~7kAw?l5SyLT*no9yMx%|_k(wD3~q;)_S_(WIGsL+ zzxc~7{o{Cp2^o%By72%al-+OQWh?AfMRd7`c2f95t_-KNUEbjeU2h;A%`#u~Tp3RHW~$$iDM}+p^^D%0E7fi8+MO)Ql18=!y9omc Uhz(q Date: Thu, 11 Jul 2024 11:17:48 +0300 Subject: [PATCH 09/65] [#16] Remove Tz fix formating Signed-off-by: Pavel Gross --- .../Services/NetmapServiceProvider.cs | 7 +- .../Services/ObjectServiceProvider.cs | 139 ++++++---- .../Services/ObjectTools.cs | 10 +- .../Tools/NetworkSettings.cs | 32 +-- src/FrostFS.SDK.Cryptography/Extentions.cs | 28 +- src/FrostFS.SDK.Cryptography/Tz/GF127.cs | 253 ------------------ src/FrostFS.SDK.Cryptography/Tz/Helper.cs | 30 --- src/FrostFS.SDK.Cryptography/Tz/SL2.cs | 179 ------------- src/FrostFS.SDK.Cryptography/Tz/TzHash.cs | 140 ---------- .../PutObjectParameters.cs | 2 + src/FrostFS.SDK.Tests/ContainerTest.cs | 1 - .../ContainerServiceMocks/GetContainerMock.cs | 1 - src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 1 - src/FrostFS.SDK.Tests/ObjectTest.cs | 3 +- src/FrostFS.SDK.Tests/SmokeTests.cs | 20 +- 15 files changed, 123 insertions(+), 723 deletions(-) delete mode 100644 src/FrostFS.SDK.Cryptography/Tz/GF127.cs delete mode 100644 src/FrostFS.SDK.Cryptography/Tz/Helper.cs delete mode 100644 src/FrostFS.SDK.Cryptography/Tz/SL2.cs delete mode 100644 src/FrostFS.SDK.Cryptography/Tz/TzHash.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 441d13b..aabc133 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -111,13 +111,16 @@ internal class NetmapServiceProvider : ContextAccessor var valueBytes = param.Value.ToByteArray(); switch (key) { + case "AuditFee": settings.AuditFee = GetLongValue(valueBytes); break; + case "BasicIncomeRate": settings.BasicIncomeRate = GetLongValue(valueBytes); break; case "ContainerFee": settings.ContainerFee = GetLongValue(valueBytes); break; + case "ContainerAliasFee": settings.ContainerAliasFee = GetLongValue(valueBytes); break; case "EpochDuration": settings.EpochDuration = GetLongValue(valueBytes); break; - case "IRCandidateFee": settings.IRCandidateFee = GetLongValue(valueBytes); break; + case "InnerRingCandidateFee": settings.InnerRingCandidateFee = GetLongValue(valueBytes); break; case "MaxECDataCount": settings.MaxECDataCount = GetLongValue(valueBytes); break; case "MaxECParityCount": settings.MaxECParityCount = GetLongValue(valueBytes); break; case "MaxObjectSize": settings.MaxObjectSize = GetLongValue(valueBytes); break; - case "WithdrawalFee": settings.WithdrawalFee = GetLongValue(valueBytes); break; + case "WithdrawFee": settings.WithdrawFee = GetLongValue(valueBytes); break; case "HomomorphicHashingDisabled": settings.HomomorphicHashingDisabled = GetBoolValue(valueBytes); break; case "MaintenanceModeAllowed": settings.MaintenanceModeAllowed = GetBoolValue(valueBytes); break; default: settings.UnnamedSettings.Add(key, valueBytes); break; diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 1937e70..bc38d7b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -73,52 +73,6 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return await GetObject(request, ctx); } - internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) - { - if (parameters.Header == null) - throw new ArgumentException("Value cannot be null", nameof(parameters.Header)); - - if (parameters.Payload == null) - throw new ArgumentException("Value cannot be null", nameof(parameters.Payload)); - - if (parameters.ClientCut) - return PutClientCutObject(parameters, ctx); - else - return PutStreamObject(parameters, ctx); - } - - internal async Task PutSingleObjectAsync(FrostFsObject modelObject, Context ctx) - { - var sessionToken = await GetOrCreateSession(ctx); - - var grpcObject = tools.CreateObject(modelObject); - - var request = new PutSingleRequest - { - Body = new PutSingleRequest.Types.Body() - { - Object = grpcObject - } - }; - - request.AddMetaHeader(); - request.AddObjectSessionToken( - sessionToken, - grpcObject.Header.ContainerId, - grpcObject.ObjectId, - ObjectSessionContext.Types.Verb.Put, - Context.Key - ); - - request.Sign(Context.Key); - - var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); - - Verifier.CheckResponse(response); - - return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); - } - internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) { var request = new DeleteRequest @@ -169,6 +123,52 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } } + internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) + { + if (parameters.Header == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Header)); + + if (parameters.Payload == null) + throw new ArgumentException("Value cannot be null", nameof(parameters.Payload)); + + if (parameters.ClientCut) + return PutClientCutObject(parameters, ctx); + else + return PutStreamObject(parameters, ctx); + } + + internal async Task PutSingleObjectAsync(FrostFsObject modelObject, Context ctx) + { + var sessionToken = await GetOrCreateSession(ctx); + + var grpcObject = tools.CreateObject(modelObject); + + var request = new PutSingleRequest + { + Body = new PutSingleRequest.Types.Body() + { + Object = grpcObject + } + }; + + request.AddMetaHeader(); + request.AddObjectSessionToken( + sessionToken, + grpcObject.Header.ContainerId, + grpcObject.ObjectId, + ObjectSessionContext.Types.Verb.Put, + Context.Key + ); + + request.Sign(Context.Key); + + var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); + } + private async Task PutClientCutObject(PutObjectParameters parameters, Context ctx) { var payloadStream = parameters.Payload!; @@ -181,24 +181,36 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); - var partSize = (int)networkSettings.MaxObjectSize; - var buffer = new byte[partSize]; + var objectSize = (int)networkSettings.MaxObjectSize; + + var fullLength = header.PayloadLength; + + if (payloadStream.CanSeek) + { + objectSize = (int)Math.Min(objectSize, payloadStream.Length); + + if (fullLength == 0) + fullLength = (ulong)payloadStream.Length; + } + + if (fullLength == 0) + throw new ArgumentException("Payload stream must be able to seek or PayloadLength must be specified"); + + var buffer = new byte[objectSize]; var largeObject = new LargeObject(header.ContainerId); var split = new Split(); - - var fullLength = (ulong)payloadStream.Length; - + while (true) { - var bytesCount = await payloadStream.ReadAsync(buffer, 0, partSize); + var bytesCount = await payloadStream.ReadAsync(buffer, 0, objectSize); split.Previous = sentObjectIds.LastOrDefault(); largeObject.AppendBlock(buffer, bytesCount); - currentObject = new FrostFsObject(header.ContainerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) + currentObject = new FrostFsObject(header.ContainerId, bytesCount < objectSize ? buffer[..bytesCount] : buffer) .SetSplit(split); if (largeObject.PayloadLength == fullLength) @@ -270,21 +282,32 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C initRequest.Sign(Context.Key); using var stream = await PutObjectInit(initRequest, ctx); - - var buffer = new byte[Constants.ObjectChunkSize]; + + var bufferSize = parameters.BufferMaxSize > 0 ? parameters.BufferMaxSize : Constants.ObjectChunkSize; + + if (payload.CanSeek) + { + bufferSize = (int)Math.Min(payload.Length, bufferSize); + } + else if (header.PayloadLength > 0) + { + bufferSize = (int)Math.Min((long)header.PayloadLength, bufferSize); + } + + var buffer = new byte[bufferSize]; while (true) { - var bufferLength = await payload.ReadAsync(buffer, 0, Constants.ObjectChunkSize, ctx.CancellationToken); + var bytesCount = await payload.ReadAsync(buffer, 0, bufferSize, ctx.CancellationToken); - if (bufferLength == 0) + if (bytesCount == 0) break; var chunkRequest = new PutRequest(initRequest) { Body = new PutRequest.Types.Body { - Chunk = ByteString.CopyFrom(buffer[..bufferLength]), + Chunk = ByteString.CopyFrom(buffer[..bytesCount]), }, VerifyHeader = null }; diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs index 35f68ae..48a1275 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs @@ -25,13 +25,9 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); grpcHeader.Version = Context.Version.ToGrpcMessage(); - - if (@object.Payload != null) - { - grpcHeader.PayloadLength = (ulong)@object.Payload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); - } - + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + var split = @object.Header.Split; if (split != null) { diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs index 03067c6..d06ce03 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs @@ -3,19 +3,19 @@ using System.Collections.Generic; namespace FrostFS.SDK.ClientV2; public class NetworkSettings - { - public ulong ContainerFee { get; internal set; } - public ulong ContainerAliasFee { get; internal set; } - public ulong InnerRingCandidateFee { get; internal set; } - public ulong WithdrawFee { get; internal set; } - public ulong EpochDuration { get; internal set; } - public ulong IRCandidateFee { get; internal set; } - public ulong MaxObjectSize { get; internal set; } - public ulong MaxECDataCount { get; internal set; } - public ulong MaxECParityCount { get; internal set; } - public ulong WithdrawalFee { get; internal set; } - public bool HomomorphicHashingDisabled { get; internal set; } - public bool MaintenanceModeAllowed { get; internal set; } - - public Dictionary UnnamedSettings { get; } = []; - } \ No newline at end of file +{ + public ulong AuditFee { get; internal set; } + public ulong BasicIncomeRate { get; internal set; } + public ulong ContainerFee { get; internal set; } + public ulong ContainerAliasFee { get; internal set; } + public ulong EpochDuration { get; internal set; } + public ulong InnerRingCandidateFee { get; internal set; } + public ulong MaxObjectSize { get; internal set; } + public ulong MaxECDataCount { get; internal set; } + public ulong MaxECParityCount { get; internal set; } + public ulong WithdrawFee { get; internal set; } + public bool HomomorphicHashingDisabled { get; internal set; } + public bool MaintenanceModeAllowed { get; internal set; } + + public Dictionary UnnamedSettings { get; } = []; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index c27f5ef..5584136 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,7 +1,5 @@ using Google.Protobuf; using Org.BouncyCastle.Crypto.Digests; -using System; -using System.Buffers.Binary; using System.Security.Cryptography; namespace FrostFS.SDK.Cryptography; @@ -20,35 +18,11 @@ public static class Extentions public static byte[] Sha256(this byte[] value) { - using var sha256 = SHA256.Create(); + var sha256 = SHA256.Create(); return sha256.ComputeHash(value); } - - internal static byte[] Sha256(this byte[] value, int offset, int count) - { - using var sha256 = SHA256.Create(); - return sha256.ComputeHash(value, offset, count); - } - - internal static byte[] Sha256(this ReadOnlySpan value) - { - using var sha256 = SHA256.Create(); - return sha256.ComputeHash(value.ToArray()); - } - public static ByteString Sha256(this IMessage data) { return ByteString.CopyFrom(data.ToByteArray().Sha256()); } - - public static ByteString Sha256(this ByteString data) - { - return ByteString.CopyFrom(data.ToByteArray().Sha256()); - } - - public static ulong Murmur64(this byte[] value, uint seed) - { - using var murmur = new Murmur3_128(seed); - return BinaryPrimitives.ReadUInt64LittleEndian(murmur.ComputeHash(value)); - } } diff --git a/src/FrostFS.SDK.Cryptography/Tz/GF127.cs b/src/FrostFS.SDK.Cryptography/Tz/GF127.cs deleted file mode 100644 index d1abdcf..0000000 --- a/src/FrostFS.SDK.Cryptography/Tz/GF127.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Security.Cryptography; - -namespace FrostFS.SDK.Cryptography.Tz; - -// GF127 represents element of GF(2^127) -public class GF127 : IEquatable -{ - public const int ByteSize = 16; - public const ulong MSB64 = (ulong)1 << 63; // 2^63 - public static readonly GF127 Zero = new(0, 0); - public static readonly GF127 One = new(1, 0); - public static readonly GF127 X127X631 = new(MSB64 + 1, MSB64); // x^127+x^63+1 - - private readonly ulong[] _data; - - public ulong this[int index] - { - get { return _data[index]; } - set { _data[index] = value; } - } - - public GF127(ulong[] value) - { - if (value is null || value.Length != 2) - throw new ArgumentException(nameof(value) + "is invalid"); - _data = value; - } - - // Constructs new element of GF(2^127) as u1*x^64 + u0. - // It is assumed that u1 has zero MSB. - public GF127(ulong u0, ulong u1) : this(new ulong[] { u0, u1 }) - { - } - - public GF127() : this(0, 0) - { - } - - public override bool Equals(object obj) - { - if (obj is null) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj is GF127 b) - return Equals(b); - return false; - } - - public override int GetHashCode() - { - return this[0].GetHashCode() + this[1].GetHashCode(); - } - - public bool Equals(GF127 other) - { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - return this[0] == other[0] && this[1] == other[1]; - } - - // return the index of MSB - private int IndexOfMSB() - { - int i = Helper.GetLeadingZeros(this[1]); - if (i == 64) - i += Helper.GetLeadingZeros(this[0]); - return 127 - i; - } - - // Set index n to 1 - public static GF127 SetN(int n) - { - if (n < 64) - return new GF127((ulong)1 << n, 0); - return new GF127(0, (ulong)1 << (n - 64)); - } - - // Add - public static GF127 operator +(GF127 a, GF127 b) - { - return new GF127(a[0] ^ b[0], a[1] ^ b[1]); - } - - // Bitwise-and - public static GF127 operator &(GF127 a, GF127 b) - { - return new GF127(a[0] & b[0], a[1] & b[1]); - } - - // Multiply - public static GF127 operator *(GF127 a, GF127 b) // 2^63 * 2, 10 - { - GF127 r = new(); - GF127 c = a; - - if (b[1] == 0) - { - for (int i = 0; i < b[0].GetNonZeroLength(); i++) - { - if ((b[0] & ((ulong)1 << i)) != 0) - r += c; - c = Mul10(c); // c = c * 2 - } - } - else - { - for (int i = 0; i < 64; i++) - { - if ((b[0] & ((ulong)1 << i)) != 0) - r += c; - c = Mul10(c); // c = c * 2 - } - - for (int i = 0; i < b[1].GetNonZeroLength(); i++) - { - if ((b[1] & ((ulong)1 << i)) != 0) - r += c; - c = Mul10(c); - } - } - - return r; - } - - // Inverse, returns a^-1 - // Extended Euclidean Algorithm - // https://link.springer.com/content/pdf/10.1007/3-540-44499-8_1.pdf - public static GF127 Inv(GF127 a) - { - GF127 v = X127X631, - u = a, - c = new(1, 0), - d = new(0, 0), - t, - x; - - int du = u.IndexOfMSB(); - int dv = v.IndexOfMSB(); - // degree of polynomial is a position of most significant bit - while (du != 0) - { - if (du < dv) - { - (v, u) = (u, v); - (dv, du) = (du, dv); - (d, c) = (c, d); - } - - x = SetN(du - dv); - t = x * v; - u += t; - // because * performs reduction on t, manually reduce u at first step - if (u.IndexOfMSB() == 127) - u += X127X631; - - t = x * d; - c += t; - - du = u.IndexOfMSB(); - dv = v.IndexOfMSB(); - } - - return c; - } - - // Mul10 returns a*x - public static GF127 Mul10(GF127 a) - { - GF127 b = new(); - var c = (a[0] & MSB64) >> 63; - b[0] = a[0] << 1; - b[1] = (a[1] << 1) ^ c; - if ((b[1] & MSB64) != 0) - { - b[0] ^= X127X631[0]; - b[1] ^= X127X631[1]; - } - - return b; - } - - // Mul11 returns a*(x+1) - public static GF127 Mul11(GF127 a) - { - GF127 b = new(); - var c = (a[0] & MSB64) >> 63; - b[0] = a[0] ^ (a[0] << 1); - b[1] = a[1] ^ (a[1] << 1) ^ c; - if ((b[1] & MSB64) == 0) return b; - b[0] ^= X127X631[0]; - b[1] ^= X127X631[1]; - return b; - } - - // Random returns random element from GF(2^127). - // Is used mostly for testing. - public static GF127 Random() - { - using RandomNumberGenerator rng = RandomNumberGenerator.Create(); - return new GF127(rng.NextUlong(), rng.NextUlong() >> 1); - } - - // FromByteArray does the deserialization stuff - public GF127 FromByteArray(byte[] data) - { - if (data.Length != ByteSize) - throw new ArgumentException( - nameof(data) + $" wrong data lenght, {nameof(GF127)} expect={ByteSize}, actual={data.Length}" - ); - var t0 = new byte[8]; - var t1 = new byte[8]; - Array.Copy(data, 0, t1, 0, 8); - Array.Copy(data, 8, t0, 0, 8); - if (BitConverter.IsLittleEndian) - { - Array.Reverse(t0); - Array.Reverse(t1); - } - - _data[0] = BitConverter.ToUInt64(t0, 0); - _data[1] = BitConverter.ToUInt64(t1, 0); - if ((_data[1] & MSB64) != 0) - throw new ArgumentException(nameof(data) + " invalid data"); - return this; - } - - // ToArray() represents element of GF(2^127) as byte array of length 16. - public byte[] ToByteArray() - { - var buff = new byte[16]; - var b0 = BitConverter.GetBytes(_data[0]); - var b1 = BitConverter.GetBytes(_data[1]); - if (BitConverter.IsLittleEndian) - { - Array.Reverse(b0); - Array.Reverse(b1); - } - - Array.Copy(b1, 0, buff, 0, 8); - Array.Copy(b0, 0, buff, 8, 8); - return buff; - } - - // ToString() returns hex-encoded representation, starting with MSB. - public override string ToString() - { - return BitConverter.ToString(ToByteArray()).Replace("-", ""); - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/Tz/Helper.cs b/src/FrostFS.SDK.Cryptography/Tz/Helper.cs deleted file mode 100644 index 807870e..0000000 --- a/src/FrostFS.SDK.Cryptography/Tz/Helper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Security.Cryptography; - -namespace FrostFS.SDK.Cryptography.Tz; - -public static class Helper -{ - public static ulong NextUlong(this RandomNumberGenerator rng) - { - var buff = new byte[8]; - rng.GetBytes(buff); - return BitConverter.ToUInt64(buff, 0); - } - - public static int GetLeadingZeros(ulong value) - { - var i = 64; - while (value != 0) - { - value >>= 1; - i--; - } - return i; - } - - public static int GetNonZeroLength(this ulong value) - { - return 64 - GetLeadingZeros(value); - } -} diff --git a/src/FrostFS.SDK.Cryptography/Tz/SL2.cs b/src/FrostFS.SDK.Cryptography/Tz/SL2.cs deleted file mode 100644 index 6802501..0000000 --- a/src/FrostFS.SDK.Cryptography/Tz/SL2.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System; -using System.Linq; - -namespace FrostFS.SDK.Cryptography.Tz; - -public class SL2 : IEquatable -{ - // 2x2 matrix - private readonly GF127[][] data; - - public static readonly SL2 ID = new( - new GF127(1, 0), new GF127(0, 0), new GF127(0, 0), new GF127(1, 0)); - - public static readonly SL2 A = new( - new GF127(2, 0), new GF127(1, 0), new GF127(1, 0), new GF127(0, 0)); - - public static readonly SL2 B = new( - new GF127(2, 0), new GF127(3, 0), new GF127(1, 0), new GF127(1, 0)); - - // Indexer - public GF127[] this[int i] - { - get { return data[i]; } - set { data[i] = value; } - } - - public SL2(GF127[][] value) - { - if (value is null || value.Length != 2 || !value.All(p => p.Length == 2)) - throw new ArgumentException(nameof(value) + $" invalid {nameof(GF127)} matrics"); - data = value; - } - - public SL2(GF127 g00, GF127 g01, GF127 g10, GF127 g11) - : this([[g00, g01], [g10, g11]]) - { - } - - public SL2() : this(GF127.One, GF127.Zero, GF127.Zero, GF127.One) - { - } - - public override bool Equals(object obj) - { - if (obj is null) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj is SL2 b) - return Equals(b); - return false; - } - - public override int GetHashCode() - { - return this[0][0].GetHashCode() + - this[0][1].GetHashCode() + - this[1][0].GetHashCode() + - this[1][1].GetHashCode(); - } - - public bool Equals(SL2 other) - { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - return this[0][0].Equals(other[0][0]) && - this[0][1].Equals(other[0][1]) && - this[1][0].Equals(other[1][0]) && - this[1][1].Equals(other[1][1]); - } - - // 2X2 matrix multiplication - public static SL2 operator *(SL2 a, SL2 b) - { - return new SL2( - a[0][0] * b[0][0] + a[0][1] * b[1][0], - a[0][0] * b[0][1] + a[0][1] * b[1][1], - a[1][0] * b[0][0] + a[1][1] * b[1][0], - a[1][0] * b[0][1] + a[1][1] * b[1][1]); - } - - // Multiplication using strassen algorithm - public static SL2 MulStrassen(SL2 a, SL2 b) - { - GF127[] t = - [ - (a[0][0] + a[1][1]) * (b[0][0] + b[1][1]), // t[0] == (a11 + a22) * (b11 + b22) - (a[1][0] + a[1][1]) * b[0][0], // t[1] == (a21 + a22) * b11 - (b[0][1] + b[1][1]) * a[0][0], // t[2] == (b12 + b22) * a11 - (b[1][0] + b[0][0]) * a[1][1], // t[3] == (b21 + b11) * a22 - (a[0][0] + a[0][1]) * b[1][1], // t[4] == (a11 + a12) * b22 - (a[1][0] + a[0][0]) * (b[0][0] + b[0][1]), // t[5] == (a21 + a11) * (b11 + b12) - (a[0][1] + a[1][1]) * (b[1][0] + b[1][1]), // t[6] == (a12 + a22) * (b21 + b22) - ]; - - SL2 r = new(); - r[0][1] = t[2] + t[4]; // r12 == a11*b12 + a11*b22 + a11*b22 + a12*b22 == a11*b12 + a12*b22 - r[1][0] = t[1] + t[3]; // r21 == a21*b11 + a22*b11 + a22*b21 + a22*b11 == a21*b11 + a22*b21 - // r11 == (a11*b11 + a22*b11` + a11*b22` + a22*b22`) + (a22*b21` + a22*b11`) + (a11*b22` + a12*b22`) + - // (a12*b21 + a22*b21` + a12*b22` + a22*b22`) == a11*b11 + a12*b21 - r[0][0] = t[0] + t[3] + t[4] + t[6]; - // r22 == (a11*b11` + a22*b11` + a11*b22` + a22*b22) + (a21*b11` + a22*b11`) + (a11*b12` + a11*b22`) + - // (a21*b11` + a11*b11` + a21*b12 + a11*b12`) == a21*b12 + a22*b22 - r[1][1] = t[0] + t[1] + t[2] + t[5]; - - return r; - } - - // Inv() returns inverse of a in SL2(GF(2^127)) - public static SL2 Inv(SL2 a) - { - GF127[] t = new GF127[2]; - t[0] = a[0][0] * a[1][1] + a[0][1] * a[1][0]; - t[1] = GF127.Inv(t[0]); - - SL2 r = new(); - r[1][1] = t[1] * a[0][0]; - r[0][1] = t[1] * a[0][1]; - r[1][0] = t[1] * a[1][0]; - r[0][0] = t[1] * a[1][1]; - - return r; - } - - // MulA() returns this*A, A = {{x, 1}, {1, 0}} - public SL2 MulA() - { - var r = new SL2(); - r[0][0] = GF127.Mul10(this[0][0]) + this[0][1]; // r11 == t11*x + t12 - r[0][1] = this[0][0]; // r12 == t11 - - r[1][0] = GF127.Mul10(this[1][0]) + this[1][1]; // r21 == t21*x + t22 - r[1][1] = this[1][0]; // r22 == t21 - - return r; - } - - // MulB() returns this*B, B = {{x, x+1}, {1, 1}} - public SL2 MulB() - { - var r = new SL2(); - r[0][0] = GF127.Mul10(this[0][0]) + this[0][1]; // r11 == t11*x + t12 - r[0][1] = GF127.Mul10(this[0][0]) + this[0][0] + this[0][1]; // r12 == t11*x + t11 + t12 - - r[1][0] = GF127.Mul10(this[1][0]) + this[1][1]; // r21 == t21*x + t22 - r[1][1] = GF127.Mul10(this[1][0]) + this[1][0] + this[1][1]; // r22 == t21*x + t21 + t22 - - return r; - } - - public SL2 FromByteArray(byte[] data) - { - if (data.Length != 64) - throw new ArgumentException(nameof(SL2) + $" invalid data, exect={64}, ecatual={data.Length}"); - this[0][0] = new GF127().FromByteArray(data[0..16]); - this[0][1] = new GF127().FromByteArray(data[16..32]); - this[1][0] = new GF127().FromByteArray(data[32..48]); - this[1][1] = new GF127().FromByteArray(data[48..64]); - return this; - } - - public byte[] ToByteArray() - { - var buff = new byte[64]; - Array.Copy(this[0][0].ToByteArray(), 0, buff, 0, 16); - Array.Copy(this[0][1].ToByteArray(), 0, buff, 16, 16); - Array.Copy(this[1][0].ToByteArray(), 0, buff, 32, 16); - Array.Copy(this[1][1].ToByteArray(), 0, buff, 48, 16); - return buff; - } - - public override string ToString() - { - return this[0][0].ToString() + this[0][1].ToString() + - this[1][0].ToString() + this[1][1].ToString(); - } -} diff --git a/src/FrostFS.SDK.Cryptography/Tz/TzHash.cs b/src/FrostFS.SDK.Cryptography/Tz/TzHash.cs deleted file mode 100644 index 7114bed..0000000 --- a/src/FrostFS.SDK.Cryptography/Tz/TzHash.cs +++ /dev/null @@ -1,140 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Security; -//using System.Security.Cryptography; - -//namespace FrostFS.SDK.Cryptography.Tz; - -//public class TzHash : HashAlgorithm -//{ -// private const int TzHashLength = 64; -// private GF127[] x; -// public override int HashSize => TzHashLength; - -// public TzHash() -// { -// Initialize(); -// } - -// public override void Initialize() -// { -// x = new GF127[4]; -// Reset(); -// HashValue = null; -// } - -// public void Reset() -// { -// x[0] = new GF127(1, 0); -// x[1] = new GF127(0, 0); -// x[2] = new GF127(0, 0); -// x[3] = new GF127(1, 0); -// } - -// public byte[] ToByteArray() -// { -// var buff = new byte[HashSize]; -// for (int i = 0; i < 4; i++) -// { -// Array.Copy(x[i].ToByteArray(), 0, buff, i * 16, 16); -// } -// return buff; -// } - -// [SecurityCritical] -// protected override void HashCore(byte[] array, int ibStart, int cbSize) -// { -// _ = HashData(array[ibStart..(ibStart + cbSize)]); -// } - -// [SecurityCritical] -// protected override byte[] HashFinal() -// { -// return HashValue = ToByteArray(); -// } - -// [SecurityCritical] -// private int HashData(byte[] data) -// { -// var n = data.Length; -// for (int i = 0; i < n; i++) -// { -// for (int j = 7; j >= 0; j--) -// { -// MulBitRight(ref x[0], ref x[1], ref x[2], ref x[3], (data[i] & (1 << j)) != 0); -// } -// } -// return n; -// } - -// // MulBitRight() multiply A (if the bit is 0) or B (if the bit is 1) on the right side -// private void MulBitRight(ref GF127 c00, ref GF127 c01, ref GF127 c10, ref GF127 c11, bool bit) -// { -// // plan 1 -// GF127 t; -// if (bit) -// { // MulB -// t = c00; -// c00 = GF127.Mul10(c00) + c01; // c00 = c00 * x + c01 -// c01 = GF127.Mul11(t) + c01; // c01 = c00 * (x+1) + c01 - -// t = c10; -// c10 = GF127.Mul10(c10) + c11; // c10 = c10 * x + c11 -// c11 = GF127.Mul11(t) + c11; // c11 = c10 * (x+1) + c11 -// } -// else -// { // MulA -// t = c00; -// c00 = GF127.Mul10(c00) + c01; // c00 = c00 * x + c01 -// c01 = t; // c01 = c00 - -// t = c10; -// c10 = GF127.Mul10(c10) + c11; // c10 = c10 * x + c11 -// c11 = t; // c11 = c10; -// } - -// //// plan 2 -// //var r = new SL2(c00, c01, c10, c11); -// //if (bit) -// // r.MulB(); -// //else -// // r.MulA(); -// } - -// // Concat() performs combining of hashes based on homomorphic characteristic. -// public static byte[] Concat(List hs) -// { -// var r = SL2.ID; -// foreach (var h in hs) -// { -// r *= new SL2().FromByteArray(h); -// } -// return r.ToByteArray(); -// } - -// // Validate() checks if hashes in hs combined are equal to h. -// public static bool Validate(byte[] h, List hs) -// { -// var expected = new SL2().FromByteArray(h); -// var actual = new SL2().FromByteArray(Concat(hs)); -// return expected.Equals(actual); -// } - -// // SubtractR() returns hash a, such that Concat(a, b) == c -// public static byte[] SubstractR(byte[] b, byte[] c) -// { -// var t1 = new SL2().FromByteArray(b); -// var t2 = new SL2().FromByteArray(c); -// var r = t2 * SL2.Inv(t1); -// return r.ToByteArray(); -// } - -// // SubtractL() returns hash b, such that Concat(a, b) == c -// public static byte[] SubstractL(byte[] a, byte[] c) -// { -// var t1 = new SL2().FromByteArray(a); -// var t2 = new SL2().FromByteArray(c); -// var r = SL2.Inv(t1) * t2; -// return r.ToByteArray(); -// } -//} diff --git a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs index 23cbc38..69a5a99 100644 --- a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs +++ b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs @@ -9,4 +9,6 @@ public class PutObjectParameters public Stream? Payload { get; set; } public bool ClientCut { get; set; } + + public int BufferMaxSize { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index d41edb8..498a47a 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,4 +1,3 @@ -using FrostFS.SDK.ClientV2; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 9a72f09..2188795 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -10,7 +10,6 @@ using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.Session; using FrostFS.Refs; -using System.Collections.Generic; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs index 6cef45f..f078df6 100644 --- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -3,7 +3,6 @@ using FrostFS.Netmap; using Grpc.Core; using FrostFS.SDK.ClientV2; using Google.Protobuf; -using NuGet.Frameworks; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 399dc07..d524284 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -45,8 +45,7 @@ public abstract class ObjectTestsBase ContainerId = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); Mocker.ObjectHeader = new(ContainerId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) - { - PayloadLength = 1, + { Version = new ModelsV2.Version(2, 13), OwnerId = OwnerId.FromKey(ecdsaKey) }; diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index 6a33209..ad9f5ee 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -63,8 +63,11 @@ public class SmokeTests var result = await fsClient.GetNodeInfoAsync(); } - [Fact] - public async void SimpleScenarioTest() + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioTest(int objectSize) { using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); @@ -88,7 +91,7 @@ public class SmokeTests Assert.NotNull(container); - var bytes = GetRandomBytes(6 * 1024 * 1024 + 100); + var bytes = GetRandomBytes(objectSize); var param = new PutObjectParameters { @@ -144,8 +147,13 @@ public class SmokeTests } } - [Fact] - public async void ClientCutScenarioTest() + [Theory] + [InlineData(1)] + [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB + [InlineData(64 * 1024 * 1024 - 1)] + [InlineData(64 * 1024 * 1024 + 1)] + [InlineData(2 * 64 * 1024 * 1024 + 256)] + public async void ClientCutScenarioTest(int objectSize) { using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); @@ -165,7 +173,7 @@ public class SmokeTests Assert.NotNull(container); - byte[] bytes = GetRandomBytes(150 * 1024 * 1024); + byte[] bytes = GetRandomBytes(objectSize); var param = new PutObjectParameters { From 7b9c19f37cdcb9670fea27f0fb0cb5fc2feba115 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 18 Jul 2024 15:08:53 +0300 Subject: [PATCH 10/65] [#17] Client: Add extra parameter API methods' parameters types with optional session, polling settings, xHeaders etc. and corresponding handlers have been added Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 136 +++---- .../Exceptions/ResponseException.cs | 12 + .../Interfaces/IFrostFSClient.cs | 32 +- .../Mappers/Object/Object.cs | 12 +- .../Session/{Session.cs => SessionMapper.cs} | 8 +- .../Parameters/IContext.cs | 11 + .../Parameters/ISessionToken.cs | 14 + .../Parameters/PrmCreateContainer.cs | 22 ++ .../Parameters/PrmCreateSession.cs | 16 + .../Parameters/PrmDeleteContainer.cs | 23 ++ .../Parameters/PrmDeleteObject.cs | 21 ++ .../Parameters/PrmGetContainer.cs | 17 + .../Parameters/PrmGetNetmapSnapshot.cs | 14 + .../Parameters/PrmGetNetworkSettings.cs | 14 + .../Parameters/PrmGetNodeInfo.cs | 14 + .../Parameters/PrmGetObject.cs | 21 ++ .../Parameters/PrmGetObjectHead.cs | 21 ++ .../Parameters/PrmListContainer.cs | 16 + .../Parameters/PrmPutObject.cs | 44 +++ .../Parameters/PrmPutSingleObject.cs | 19 + .../Parameters/PrmSearchObject.cs | 26 ++ .../Parameters/PrmWait.cs | 23 ++ .../Services/ContainerServiceProvider.cs | 120 ++++-- .../Services/NetmapServiceProvider.cs | 24 +- .../Services/ObjectServiceProvider.cs | 190 ++++++---- .../Services/SessionServiceProvider.cs | 24 +- src/FrostFS.SDK.ClientV2/Tools/Object.cs | 9 +- .../{Services => Tools}/ObjectReader.cs | 0 .../{Services => Tools}/ObjectStreamer.cs | 0 .../{Services => Tools}/ObjectTools.cs | 2 +- .../Tools/RequestConstructor.cs | 45 ++- .../{Services => Tools}/SearchReader.cs | 0 src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 2 +- src/FrostFS.SDK.ModelsV2/Context.cs | 2 - .../FrostFS.SDK.ModelsV2.csproj | 5 + .../Object/FrostFsObject.cs | 50 ++- .../PutObjectParameters.cs | 13 - src/FrostFS.SDK.ModelsV2/SessionToken.cs | 9 +- src/FrostFS.SDK.Tests/ContainerTest.cs | 23 +- .../ContainerServiceMocks/GetContainerMock.cs | 29 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 11 +- src/FrostFS.SDK.Tests/SmokeTests.cs | 346 +++++++++++++----- 42 files changed, 1054 insertions(+), 386 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs rename src/FrostFS.SDK.ClientV2/Mappers/Session/{Session.cs => SessionMapper.cs} (62%) create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/IContext.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs rename src/FrostFS.SDK.ClientV2/{Services => Tools}/ObjectReader.cs (100%) rename src/FrostFS.SDK.ClientV2/{Services => Tools}/ObjectStreamer.cs (100%) rename src/FrostFS.SDK.ClientV2/{Services => Tools}/ObjectTools.cs (98%) rename src/FrostFS.SDK.ClientV2/{Services => Tools}/SearchReader.cs (100%) diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 6e9cbd0..6d09283 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -2,6 +2,7 @@ using FrostFS.Netmap; using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Parameters; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; @@ -96,7 +97,8 @@ public class Client : IFrostFSClient channel: channel, version: new Version(2, 13)); - CheckFrostFsVersionSupport(); + // TODO: define timeout logic + CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20)}); } public void Dispose() @@ -115,105 +117,106 @@ public class Client : IFrostFSClient } #region ContainerImplementation - public Task GetContainerAsync(ContainerId containerId, Context? ctx = null) + public Task GetContainerAsync(PrmGetContainer args) { - var service = GetContainerService(ref ctx); - return service.GetContainerAsync(containerId, ctx!); + var service = GetContainerService(args); + return service.GetContainerAsync(args); } - public IAsyncEnumerable ListContainersAsync(Context? ctx = default) + public IAsyncEnumerable ListContainersAsync(PrmListContainer? args = null) { - var service = GetContainerService(ref ctx); - return service.ListContainersAsync(ctx!); + args = args ?? new PrmListContainer(); + var service = GetContainerService(args); + return service.ListContainersAsync(args); } - public Task CreateContainerAsync(ModelsV2.Container container, Context? ctx = null) + public Task CreateContainerAsync(PrmCreateContainer args) { - var service = GetContainerService(ref ctx); - return service.CreateContainerAsync(container, ctx!); + var service = GetContainerService(args); + return service.CreateContainerAsync(args); } - public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default) + public Task DeleteContainerAsync(PrmDeleteContainer args) { - var service = GetContainerService(ref ctx); - return service.DeleteContainerAsync(containerId, ctx!); + var service = GetContainerService(args); + return service.DeleteContainerAsync(args); } #endregion #region NetworkImplementation - public Task GetNetmapSnapshotAsync(Context? ctx = default) + public Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args) { - var service = GetNetmapService(ref ctx); - return service.GetNetmapSnapshotAsync(ctx!); + args ??= new PrmGetNetmapSnapshot(); + var service = GetNetmapService(args); + return service.GetNetmapSnapshotAsync(args); } - public Task GetNodeInfoAsync(Context? ctx = default) + public Task GetNodeInfoAsync(PrmGetNodeInfo? args) { - var service = GetNetmapService(ref ctx); - return service.GetLocalNodeInfoAsync(ctx!); + args ??= new PrmGetNodeInfo(); + var service = GetNetmapService(args); + return service.GetLocalNodeInfoAsync(args); } - public Task GetNetworkSettingsAsync(Context? ctx = default) - { - var service = GetNetmapService(ref ctx); - return service.GetNetworkSettingsAsync(ctx!); + public Task GetNetworkSettingsAsync(PrmGetNetworkSettings? args) + { + args ??= new PrmGetNetworkSettings(); + var service = GetNetmapService(args); + return service.GetNetworkSettingsAsync(args.Context!); } #endregion #region ObjectImplementation - public Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + public Task GetObjectHeadAsync(PrmGetObjectHead args) { - var service = GetObjectService(ref ctx); - return service.GetObjectHeadAsync(containerId, objectId, ctx!); + var service = GetObjectService(args); + return service.GetObjectHeadAsync(args); } - public Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + public Task GetObjectAsync(PrmGetObject args) { - var service = GetObjectService(ref ctx); - return service.GetObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(args); + return service.GetObjectAsync(args); } - public Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default) + public Task PutObjectAsync(PrmPutObject args) { - var service = GetObjectService(ref ctx); - return service.PutObjectAsync(putObjectParameters, ctx!); + var service = GetObjectService(args); + return service.PutObjectAsync(args); } - public Task PutSingleObjectAsync(FrostFsObject obj, Context? ctx = default) + public Task PutSingleObjectAsync(PrmPutSingleObject args) { - var service = GetObjectService(ref ctx); - return service.PutSingleObjectAsync(obj, ctx!); + var service = GetObjectService(args); + return service.PutSingleObjectAsync(args); } - public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default) + public Task DeleteObjectAsync(PrmDeleteObject args) { - var service = GetObjectService(ref ctx); - return service.DeleteObjectAsync(containerId, objectId, ctx!); + var service = GetObjectService(args); + return service.DeleteObjectAsync(args); } - public IAsyncEnumerable SearchObjectsAsync( - ContainerId containerId, - IEnumerable filters, - Context? ctx = default) + public IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args) { - var service = GetObjectService(ref ctx); - return service.SearchObjectsAsync(containerId, filters, ctx!); + var service = GetObjectService(args); + return service.SearchObjectsAsync(args); } #endregion #region SessionImplementation - public async Task CreateSessionAsync(ulong expiration, Context? ctx = null) + public async Task CreateSessionAsync(PrmCreateSession args) { - var session = await CreateSessionInternalAsync(expiration, ctx); + var session = await CreateSessionInternalAsync(args); var token = session.Serialize(); - return new ModelsV2.SessionToken([], token); + return new ModelsV2.SessionToken(token); } - public Task CreateSessionInternalAsync(ulong expiration, Context? ctx = null) + internal Task CreateSessionInternalAsync(PrmCreateSession args) { - var service = GetSessionService(ref ctx); - return service.CreateSessionAsync(expiration, ctx!); + var service = GetSessionService(args); + return service.CreateSessionAsync(args); } #endregion @@ -226,8 +229,9 @@ public class Client : IFrostFSClient private async void CheckFrostFsVersionSupport(Context? ctx = default) { - var service = GetNetmapService(ref ctx); - var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!); + var args = new PrmGetNodeInfo(ctx); + var service = GetNetmapService(args); + var localNodeInfo = await service.GetLocalNodeInfoAsync(args); if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { @@ -237,24 +241,24 @@ public class Client : IFrostFSClient } } - private CallInvoker? SetupEnvironment(ref Context? ctx) + private CallInvoker? SetupEnvironment(IContext ctx) { if (isDisposed) throw new Exception("Client is disposed."); - ctx ??= new Context(); + ctx.Context ??= new Context(); CallInvoker? callInvoker = null; - if (ctx.Interceptors != null && ctx.Interceptors.Count > 0) + if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0) { - foreach (var interceptor in ctx.Interceptors) + foreach (var interceptor in ctx.Context.Interceptors) { callInvoker = AddInvoker(callInvoker, interceptor); } } - if (ctx.Callback != null) - callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Callback)); + if (ctx.Context.Callback != null) + callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback)); return callInvoker; @@ -269,9 +273,9 @@ public class Client : IFrostFSClient } } - private NetmapServiceProvider GetNetmapService(ref Context? ctx) + private NetmapServiceProvider GetNetmapService(IContext ctx) { - var callInvoker = SetupEnvironment(ref ctx); + var callInvoker = SetupEnvironment(ctx); var client = NetmapServiceClient ?? (callInvoker != null ? new NetmapService.NetmapServiceClient(callInvoker) : new NetmapService.NetmapServiceClient(ClientCtx.Channel)); @@ -279,9 +283,9 @@ public class Client : IFrostFSClient return new NetmapServiceProvider(client, ClientCtx); } - private SessionServiceProvider GetSessionService(ref Context? ctx) + private SessionServiceProvider GetSessionService(IContext ctx) { - var callInvoker = SetupEnvironment(ref ctx); + var callInvoker = SetupEnvironment(ctx); var client = SessionServiceClient ?? (callInvoker != null ? new SessionService.SessionServiceClient(callInvoker) : new SessionService.SessionServiceClient(ClientCtx.Channel)); @@ -289,9 +293,9 @@ public class Client : IFrostFSClient return new SessionServiceProvider(client, ClientCtx); } - private ContainerServiceProvider GetContainerService(ref Context? ctx) + private ContainerServiceProvider GetContainerService(IContext ctx) { - var callInvoker = SetupEnvironment(ref ctx); + var callInvoker = SetupEnvironment(ctx); var client = ContainerServiceClient ?? (callInvoker != null ? new ContainerService.ContainerServiceClient(callInvoker) : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); @@ -299,9 +303,9 @@ public class Client : IFrostFSClient return new ContainerServiceProvider(client, ClientCtx); } - private ObjectServiceProvider GetObjectService(ref Context? ctx) + private ObjectServiceProvider GetObjectService(IContext ctx) { - var callInvoker = SetupEnvironment(ref ctx); + var callInvoker = SetupEnvironment(ctx); var client = ObjectServiceClient ?? (callInvoker != null ? new ObjectService.ObjectServiceClient(callInvoker) : new ObjectService.ObjectServiceClient(ClientCtx.Channel)); diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs new file mode 100644 index 0000000..7772e1e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs @@ -0,0 +1,12 @@ +using System; +using FrostFS.SDK.ModelsV2; + +public class ResponseException : Exception +{ + public Status Status { get; set; } + + public ResponseException(Status status) : base() + { + Status = status; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index bed4136..a483abc 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -1,52 +1,50 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; - +using FrostFS.SDK.ClientV2.Parameters; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Netmap; - namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable { #region Network - Task GetNetmapSnapshotAsync(Context? context = default); + Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args = null); - Task GetNodeInfoAsync(Context? context = default); + Task GetNodeInfoAsync(PrmGetNodeInfo? args = null); - Task GetNetworkSettingsAsync(Context? context = default); + Task GetNetworkSettingsAsync(PrmGetNetworkSettings? args = null); #endregion #region Session - Task CreateSessionAsync(ulong expiration, Context? context = default); + Task CreateSessionAsync(PrmCreateSession args); #endregion #region Container - Task GetContainerAsync(ContainerId containerId, Context? context = default); + Task GetContainerAsync(PrmGetContainer args); - IAsyncEnumerable ListContainersAsync(Context? context = default); + IAsyncEnumerable ListContainersAsync(PrmListContainer? args = null); - Task CreateContainerAsync(ModelsV2.Container container, Context? context = default); + Task CreateContainerAsync(PrmCreateContainer args); - Task DeleteContainerAsync(ContainerId containerId, Context? context = default); + Task DeleteContainerAsync(PrmDeleteContainer args); #endregion #region Object - Task GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectHeadAsync(PrmGetObjectHead args); - Task GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task GetObjectAsync(PrmGetObject args); - Task PutObjectAsync(PutObjectParameters putObjectParameters, Context? context = default); + Task PutObjectAsync(PrmPutObject putObjectParameters); - Task PutSingleObjectAsync(FrostFsObject obj, Context? context = default); + Task PutSingleObjectAsync(PrmPutSingleObject args); - Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? context = default); + Task DeleteObjectAsync(PrmDeleteObject args); - IAsyncEnumerable SearchObjectsAsync(ContainerId cid, IEnumerable filters, Context? context = default); + IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args); #endregion #region Tools ObjectId CalculateObjectId(ObjectHeader header); #endregion } - diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs index 2804b65..bda31fe 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -2,13 +2,13 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; -public static class ObjectMapper +internal static class ObjectMapper { - public static FrostFsObject ToModel(this Object.Object obj) + internal static FrostFsObject ToModel(this Object.Object obj) { - return new FrostFsObject( - ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()), - obj.Header.ToModel(), - obj.Payload.ToByteArray()); + return new FrostFsObject(obj.Header.ToModel()) + { + ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()) + }; } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs similarity index 62% rename from src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs rename to src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs index e565891..97b1c69 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/Session.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs @@ -4,7 +4,7 @@ namespace FrostFS.SDK.ClientV2; public static class SessionMapper { - internal static byte[] Serialize(this Session.SessionToken token) + public static byte[] Serialize(this Session.SessionToken token) { byte[] bytes = new byte[token.CalculateSize()]; CodedOutputStream stream = new(bytes); @@ -13,11 +13,9 @@ public static class SessionMapper return bytes; } - internal static Session.SessionToken DeserializeSessionToken(this byte[] bytes) + public static Session.SessionToken Deserialize(this Session.SessionToken token, byte[] bytes) { - Session.SessionToken token = new(); token.MergeFrom(bytes); - return token; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs new file mode 100644 index 0000000..c48dc8a --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs @@ -0,0 +1,11 @@ +namespace FrostFS.SDK.ClientV2.Parameters; + +public interface IContext +{ + /// + /// The method call can be extended with additional behavior like canceling by timeout or user's request, + /// callbacks, interceptors. + /// + /// Additional parameters for calling the method + Context? Context { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs b/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs new file mode 100644 index 0000000..c5ac1da --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs @@ -0,0 +1,14 @@ +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public interface ISessionToken +{ + /// + /// Object represents token of the FrostFS Object session. A session is opened between any two sides of the + /// system, and implements a mechanism for transferring the power of attorney of actions to another network + /// member. The session has a limited validity period, and applies to a strictly defined set of operations. + /// + /// Instance of the session obtained from the server + SessionToken? SessionToken { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs new file mode 100644 index 0000000..9209a03 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs @@ -0,0 +1,22 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmCreateContainer(ModelsV2.Container container) : IContext +{ + public ModelsV2.Container Container { get; set; } = container; + + /// + /// Since the container becomes available with some delay, it needs to poll the container status + /// + /// Rules for polling the result + public PrmWait? WaitParams { get; set; } + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs new file mode 100644 index 0000000..870a481 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs @@ -0,0 +1,16 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmCreateSession(ulong expiration, Context? context = default) : IContext +{ + public ulong Expiration { get; set; } = expiration; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = context; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs new file mode 100644 index 0000000..4451b00 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs @@ -0,0 +1,23 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmDeleteContainer(ContainerId containerId, Context? ctx = null) : IContext +{ + public ContainerId ContainerId { get; set; } = containerId; + + /// + /// Since the container is removed with some delay, it needs to poll the container status + /// + /// Rules for polling the result + public PrmWait? WaitParams { get; set; } + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = ctx; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs new file mode 100644 index 0000000..8241f64 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs @@ -0,0 +1,21 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmDeleteObject(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +{ + public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs new file mode 100644 index 0000000..59350fe --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs @@ -0,0 +1,17 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetContainer(ContainerId containerId, Context? ctx = null) : IContext +{ + public ContainerId ContainerId { get; set; } = containerId; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = ctx; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs new file mode 100644 index 0000000..e3e28c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs @@ -0,0 +1,14 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetNetmapSnapshot(Context? context = default) : IContext +{ + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = context; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs new file mode 100644 index 0000000..8db583e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs @@ -0,0 +1,14 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetNetworkSettings(Context? context = default) : IContext +{ + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = context; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs new file mode 100644 index 0000000..78a7697 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs @@ -0,0 +1,14 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetNodeInfo(Context? context = default) : IContext +{ + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = context; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs new file mode 100644 index 0000000..5ee1fd9 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs @@ -0,0 +1,21 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetObject(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +{ + public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs new file mode 100644 index 0000000..88287a8 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs @@ -0,0 +1,21 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmGetObjectHead(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +{ + public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs new file mode 100644 index 0000000..3d4c1a2 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs @@ -0,0 +1,16 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmListContainer() : IContext +{ + public string SessionToken { get; set; } = string.Empty; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs new file mode 100644 index 0000000..7e4999d --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs @@ -0,0 +1,44 @@ +using System.Collections.Specialized; +using System.IO; +using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmPutObject : IContext, ISessionToken +{ + /// + /// Need to provide values like ContainerId and ObjectType to create and object. + /// Optional parameters ike Attributes can be provided as well. + /// + /// Header with required parameters to create an object + public ObjectHeader? Header { get; set; } + + /// + /// A stream with source data + /// + public Stream? Payload { get; set; } + + /// + /// Object size is limited. In the data exceeds the limit, the object will be splitted. + /// If the parameter is true, the client side cut is applied. Otherwise, the data is transferred + /// as a stream and will be cut on server side. + /// + /// Is client cut is applied + public bool ClientCut { get; set; } + + /// + /// Overrides default size of the buffer for stream transferring. + /// + /// Size of the buffer + public int BufferMaxSize { get; set; } + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs new file mode 100644 index 0000000..95e514f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs @@ -0,0 +1,19 @@ +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmPutSingleObject(FrostFsObject frostFsObject, Context? context = null) : IContext, ISessionToken +{ + public FrostFsObject FrostFsObject { get; set; } = frostFsObject; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } = context; + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs new file mode 100644 index 0000000..8c0ad8c --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2.Parameters; + +public sealed class PrmSearchObject(ContainerId containerId, params ObjectFilter[] filters) : IContext, ISessionToken +{ + public ContainerId ContainerId { get; set; } = containerId; + + /// + /// Defines the search criteria + /// + /// Collection of filters + public IEnumerable Filters { get; set; } = filters; + + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } + + /// + public SessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs new file mode 100644 index 0000000..86e32d7 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs @@ -0,0 +1,23 @@ +using System; +namespace FrostFS.SDK.ClientV2.Parameters; + +public class PrmWait(TimeSpan timeout, TimeSpan pollInterval) +{ + private static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(120); + private static TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(5); + + public PrmWait(int timeout, int interval) : this(TimeSpan.FromSeconds(timeout), TimeSpan.FromSeconds(interval)) + { + } + + public static PrmWait DefaultParams { get; } = new PrmWait(DefaultTimeout, DefaultPollInterval); + + public TimeSpan Timeout { get; set; } = timeout.Ticks == 0 ? DefaultTimeout : timeout; + + public TimeSpan PollInterval { get; set; } = pollInterval.Ticks == 0 ? DefaultPollInterval : pollInterval; + + public DateTime GetDeadline() + { + return DateTime.UtcNow.AddTicks(Timeout.Ticks); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 6a3036b..dfbd614 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -6,6 +6,10 @@ 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; namespace FrostFS.SDK.ClientV2; @@ -19,28 +23,21 @@ internal class ContainerServiceProvider : ContextAccessor containerServiceClient = service; } - internal async Task GetContainerAsync(ContainerId cid, Context context) + internal async Task GetContainerAsync(PrmGetContainer args) { - var request = new GetRequest - { - Body = new GetRequest.Types.Body - { - ContainerId = cid.ToGrpcMessage() - }, - }; + GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders); - request.AddMetaHeader(); - request.Sign(Context.Key); - - var response = await containerServiceClient.GetAsync(request, null, context.Deadline, context.CancellationToken); + var response = await containerServiceClient.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken); Verifier.CheckResponse(response); - return response.Body.Container.ToModel(); + return response.Body.Container.ToModel(); } - internal async IAsyncEnumerable ListContainersAsync(Context ctx) + internal async IAsyncEnumerable ListContainersAsync(PrmListContainer args) { + var ctx = args.Context!; + var request = new ListRequest { Body = new ListRequest.Types.Body @@ -49,7 +46,7 @@ internal class ContainerServiceProvider : ContextAccessor } }; - request.AddMetaHeader(); + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); var response = await containerServiceClient.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -62,9 +59,10 @@ internal class ContainerServiceProvider : ContextAccessor } } - internal async Task CreateContainerAsync(ModelsV2.Container container, Context ctx) + internal async Task CreateContainerAsync(PrmCreateContainer args) { - var grpcContainer = container.ToGrpcMessage(); + var ctx = args.Context!; + var grpcContainer = args.Container.ToGrpcMessage(); grpcContainer.OwnerId = Context.Owner.ToGrpcMessage(); grpcContainer.Version = Context.Version.ToGrpcMessage(); @@ -73,37 +71,113 @@ internal class ContainerServiceProvider : ContextAccessor Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = Context.Key.SignRFC6979(grpcContainer), + Signature = Context.Key.SignRFC6979(grpcContainer) } }; - request.AddMetaHeader(); + + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); var response = await containerServiceClient.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); + + await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx); return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); } - internal async Task DeleteContainerAsync(ContainerId cid, Context ctx) + internal async Task DeleteContainerAsync(PrmDeleteContainer args) { + var ctx = args.Context!; var request = new DeleteRequest { Body = new DeleteRequest.Types.Body { - ContainerId = cid.ToGrpcMessage(), - Signature = Context.Key.SignRFC6979(cid.ToGrpcMessage().Value) + ContainerId = args.ContainerId.ToGrpcMessage(), + Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value) } }; - request.AddMetaHeader(); + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); var response = await containerServiceClient.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx); Verifier.CheckResponse(response); } + + private GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders) + { + var request = new GetRequest + { + Body = new GetRequest.Types.Body + { + ContainerId = id + } + }; + + request.AddMetaHeader(xHeaders); + request.Sign(Context.Key); + + return request; + } + + private enum WaitExpects + { + Exists, + Removed + } + + private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx) + { + var request = GetContainerRequest(id, null); + + async Task action() + { + var response = await containerServiceClient.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken); + Verifier.CheckResponse(response); + } + + await WaitFor(action, expect, waitParams); + } + + private static async Task WaitFor( + Func action, + WaitExpects expect, + PrmWait? waitParams) + { + waitParams ??= PrmWait.DefaultParams; + var deadLine = waitParams.GetDeadline(); + + while (true) + { + try + { + await action(); + + if (expect == WaitExpects.Exists) + return; + + await Task.Delay(waitParams.PollInterval); + } + catch (ResponseException ex) + { + if (DateTime.UtcNow >= deadLine) + throw new TimeoutException(); + + if (ex.Status.Code != ModelsV2.Enums.StatusCode.ContainerNotFound) + throw; + + if (expect == WaitExpects.Removed) + return; + + await Task.Delay(waitParams.PollInterval); + } + } + } } diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index aabc133..0d0a6d3 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -7,6 +7,7 @@ using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; using FrostFS.SDK.ModelsV2.Netmap; using static FrostFS.Netmap.NetworkConfig.Types; +using FrostFS.SDK.ClientV2.Parameters; namespace FrostFS.SDK.ClientV2; @@ -39,14 +40,15 @@ internal class NetmapServiceProvider : ContextAccessor return settings; } - internal async Task GetLocalNodeInfoAsync(Context ctx) + internal async Task GetLocalNodeInfoAsync(PrmGetNodeInfo args) { + var ctx = args.Context!; var request = new LocalNodeInfoRequest { Body = new LocalNodeInfoRequest.Types.Body { } }; - request.AddMetaHeader(); + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -58,12 +60,9 @@ internal class NetmapServiceProvider : ContextAccessor internal async Task GetNetworkInfoAsync(Context ctx) { - var request = new NetworkInfoRequest - { - Body = new NetworkInfoRequest.Types.Body { } - }; + var request = new NetworkInfoRequest(); - request.AddMetaHeader(); + request.AddMetaHeader(null); request.Sign(Context.Key); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -73,14 +72,13 @@ internal class NetmapServiceProvider : ContextAccessor return response; } - internal async Task GetNetmapSnapshotAsync(Context ctx) + internal async Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot args) { - var request = new NetmapSnapshotRequest - { - Body = new NetmapSnapshotRequest.Types.Body { } - }; + var ctx = args.Context!; - request.AddMetaHeader(); + var request = new NetmapSnapshotRequest(); + + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index bc38d7b..4d4cece 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -12,6 +12,8 @@ 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; @@ -19,21 +21,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { readonly ObjectTools tools = new(ctx); - internal async Task GetObjectHeadAsync(ContainerId cid, ObjectId oid, Context ctx) + internal async Task GetObjectHeadAsync(PrmGetObjectHead args) { + var ctx = args.Context!; var request = new HeadRequest { Body = new HeadRequest.Types.Body { Address = new Address { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() + ContainerId = args.ContainerId.ToGrpcMessage(), + ObjectId = args.ObjectId.ToGrpcMessage() } } }; - request.AddMetaHeader(); + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + request.Body.Address, + ObjectSessionContext.Types.Verb.Head, + Context.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); + request.Sign(Context.Key); var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -43,51 +54,59 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return response.Body.Header.Header.ToModel(); } - internal async Task GetObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + internal async Task GetObjectAsync(PrmGetObject args) { - var sessionToken = await GetOrCreateSession(ctx); - + var ctx = args.Context!; + var request = new GetRequest { Body = new GetRequest.Types.Body { Address = new Address { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() + ContainerId = args.ContainerId.ToGrpcMessage(), + ObjectId = args.ObjectId.ToGrpcMessage() } } }; - request.AddMetaHeader(); - request.AddObjectSessionToken( - sessionToken, - cid.ToGrpcMessage(), - oid.ToGrpcMessage(), + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + request.Body.Address, ObjectSessionContext.Types.Verb.Get, - Context.Key - ); + Context.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(Context.Key); return await GetObject(request, ctx); } - internal async Task DeleteObjectAsync(ContainerId cid, ObjectId oid, Context ctx) + internal async Task DeleteObjectAsync(PrmDeleteObject args) { + var ctx = args.Context!; var request = new DeleteRequest { Body = new DeleteRequest.Types.Body { Address = new Address { - ContainerId = cid.ToGrpcMessage(), - ObjectId = oid.ToGrpcMessage() + ContainerId = args.ContainerId.ToGrpcMessage(), + ObjectId = args.ObjectId.ToGrpcMessage() } } }; - request.AddMetaHeader(); + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + request.Body.Address, + ObjectSessionContext.Types.Verb.Delete, + Context.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(Context.Key); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -95,24 +114,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Verifier.CheckResponse(response); } - internal async IAsyncEnumerable SearchObjectsAsync( - ContainerId cid, - IEnumerable filters, - Context ctx) + internal async IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args) { + var ctx = args.Context!; var request = new SearchRequest { Body = new SearchRequest.Types.Body { - ContainerId = cid.ToGrpcMessage(), + ContainerId = args.ContainerId.ToGrpcMessage(), Filters = { }, Version = 1 // TODO: clarify this param } }; - request.Body.Filters.AddRange(filters.Select(f => f.ToGrpcMessage())); + request.Body.Filters.AddRange(args.Filters.Select(f => f.ToGrpcMessage())); - request.AddMetaHeader(); + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + new Address { ContainerId = request.Body.ContainerId }, + ObjectSessionContext.Types.Verb.Search, + Context.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); + request.Sign(Context.Key); var objectsIds = SearchObjects(request, ctx); @@ -123,25 +148,24 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } } - internal Task PutObjectAsync(PutObjectParameters parameters, Context ctx) + internal Task PutObjectAsync(PrmPutObject args) { - if (parameters.Header == null) - throw new ArgumentException("Value cannot be null", nameof(parameters.Header)); + if (args.Header == null) + throw new ArgumentException("Value cannot be null", nameof(args.Header)); - if (parameters.Payload == null) - throw new ArgumentException("Value cannot be null", nameof(parameters.Payload)); + if (args.Payload == null) + throw new ArgumentException("Value cannot be null", nameof(args.Payload)); - if (parameters.ClientCut) - return PutClientCutObject(parameters, ctx); + if (args.ClientCut) + return PutClientCutObject(args); else - return PutStreamObject(parameters, ctx); + return PutStreamObject(args); } - internal async Task PutSingleObjectAsync(FrostFsObject modelObject, Context ctx) + internal async Task PutSingleObjectAsync(PrmPutSingleObject args) { - var sessionToken = await GetOrCreateSession(ctx); - - var grpcObject = tools.CreateObject(modelObject); + var ctx = args.Context!; + var grpcObject = tools.CreateObject(args.FrostFsObject); var request = new PutSingleRequest { @@ -151,14 +175,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - request.AddMetaHeader(); - request.AddObjectSessionToken( - sessionToken, - grpcObject.Header.ContainerId, - grpcObject.ObjectId, + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId}, ObjectSessionContext.Types.Verb.Put, - Context.Key - ); + Context.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(Context.Key); @@ -169,17 +193,23 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } - private async Task PutClientCutObject(PutObjectParameters parameters, Context ctx) + static readonly AsyncLocal asyncLocalSession = new (); + + private async Task PutClientCutObject(PrmPutObject args) { - var payloadStream = parameters.Payload!; - var header = parameters.Header!; + var ctx = args.Context!; + var tokenRaw = await GetOrCreateSession(args, ctx); + var token = new ModelsV2.SessionToken(tokenRaw.Serialize()); + + var payloadStream = args.Payload!; + var header = args.Header!; ObjectId? objectId; List sentObjectIds = []; FrostFsObject? currentObject; - var networkSettings = await Context.Client.GetNetworkSettingsAsync(ctx); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmGetNetworkSettings(ctx)); var objectSize = (int)networkSettings.MaxObjectSize; @@ -208,27 +238,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C split.Previous = sentObjectIds.LastOrDefault(); - largeObject.AppendBlock(buffer, bytesCount); + largeObject.Header.PayloadLength += (ulong)bytesCount; - currentObject = new FrostFsObject(header.ContainerId, bytesCount < objectSize ? buffer[..bytesCount] : buffer) + currentObject = new FrostFsObject(header.ContainerId) + .SetPayload(bytesCount < objectSize ? buffer[..bytesCount] : buffer) .SetSplit(split); if (largeObject.PayloadLength == fullLength) break; - objectId = await PutSingleObjectAsync(currentObject, ctx); - + objectId = await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx) { SessionToken = token }); + sentObjectIds.Add(objectId!); } if (sentObjectIds.Count != 0) { - largeObject.AddAttributes(parameters.Header!.Attributes); - largeObject.CalculateHash(); - + largeObject.AddAttributes(args.Header!.Attributes); + currentObject.SetParent(largeObject); - objectId = await PutSingleObjectAsync(currentObject, ctx); + var putSingleObjectParams = new PrmPutSingleObject(currentObject, ctx) { SessionToken = token }; + + objectId = await PutSingleObjectAsync(putSingleObjectParams); + sentObjectIds.Add(objectId); var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) @@ -236,22 +269,21 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C linkObject.Header.Attributes.Clear(); - _ = await PutSingleObjectAsync(linkObject, ctx); + _ = await PutSingleObjectAsync(new PrmPutSingleObject(linkObject, ctx){ SessionToken = token }); return tools.CalculateObjectId(largeObject.Header); } - currentObject.AddAttributes(parameters.Header!.Attributes); + currentObject.AddAttributes(args.Header!.Attributes); - return await PutSingleObjectAsync(currentObject, ctx); + return await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx)); } - private async Task PutStreamObject(PutObjectParameters parameters, Context ctx) + private async Task PutStreamObject(PrmPutObject args) { - var payload = parameters.Payload!; - var header = parameters.Header!; - - var sessionToken = await GetOrCreateSession(ctx); + var ctx = args.Context!; + var payload = args.Payload!; + var header = args.Header!; var hdr = header.ToGrpcMessage(); hdr.OwnerId = Context.Owner.ToGrpcMessage(); @@ -270,20 +302,21 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - initRequest.AddMetaHeader(); - initRequest.AddObjectSessionToken( - sessionToken, - hdr.ContainerId, - oid, + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + new Address { ContainerId = hdr.ContainerId, ObjectId = oid }, ObjectSessionContext.Types.Verb.Put, Context.Key ); + initRequest.AddMetaHeader(args.XHeaders, sessionToken); + initRequest.Sign(Context.Key); using var stream = await PutObjectInit(initRequest, ctx); - var bufferSize = parameters.BufferMaxSize > 0 ? parameters.BufferMaxSize : Constants.ObjectChunkSize; + var bufferSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; if (payload.CanSeek) { @@ -307,12 +340,13 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { Body = new PutRequest.Types.Body { - Chunk = ByteString.CopyFrom(buffer[..bytesCount]), + Chunk = ByteString.CopyFrom(buffer.AsSpan()[..bytesCount]), }, VerifyHeader = null }; chunkRequest.Sign(Context.Key); + await stream.Write(chunkRequest); } @@ -388,13 +422,13 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return new SearchReader(call); } - private async Task GetOrCreateSession(Context ctx) + private async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) { - if (string.IsNullOrEmpty(ctx.SessionToken)) + if (args.SessionToken is null) { - return await Context.Client.CreateSessionInternalAsync(uint.MaxValue, ctx); + return await Context.Client.CreateSessionInternalAsync(new PrmCreateSession(uint.MaxValue, ctx)); } - - return Convert.FromBase64String(ctx.SessionToken).DeserializeSessionToken(); + + return new Session.SessionToken().Deserialize(args.SessionToken.Token); } } diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index c8c3c47..64c6597 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; - using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2.Parameters; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; @@ -15,41 +15,41 @@ internal class SessionServiceProvider : ContextAccessor _sessionServiceClient = sessionServiceClient; } - internal async Task CreateSessionAsync(ulong expiration, Context ctx) + internal async Task CreateSessionAsync(PrmCreateSession args) { var request = new CreateRequest { Body = new CreateRequest.Types.Body { OwnerId = Context.Owner.ToGrpcMessage(), - Expiration = expiration + Expiration = args.Expiration } }; - request.AddMetaHeader(); + request.AddMetaHeader(args.XHeaders); request.Sign(Context.Key); - var token = await CreateSession(request, ctx); - - return token; + return await CreateSession(request, args.Context!); } internal async Task CreateSession(CreateRequest request, Context ctx) { - var resp = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); return new SessionToken { Body = new SessionToken.Types.Body { - Id = resp.Body.Id, - SessionKey = resp.Body.SessionKey, + Id = response.Body.Id, + SessionKey = response.Body.SessionKey, OwnerId = request.Body.OwnerId, Lifetime = new SessionToken.Types.Body.Types.TokenLifetime { Exp = request.Body.Expiration, - Iat = resp.MetaHeader.Epoch, - Nbf = resp.MetaHeader.Epoch, + Iat = response.MetaHeader.Epoch, + Nbf = response.MetaHeader.Epoch, } } }; diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs index 0a0290e..1bc66a3 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -13,6 +13,12 @@ public static class ObjectExtensions return obj; } + public static FrostFsObject SetPayload(this FrostFsObject obj, byte[] bytes) + { + obj.Payload = bytes; + return obj; + } + public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value) { obj.AddAttribute(new ObjectAttribute(key, value)); @@ -45,9 +51,6 @@ public static class ObjectExtensions public static FrostFsObject CalculateObjectId(this FrostFsObject obj) { - if (obj.Payload == null) - throw new MissingFieldException("Payload cannot be null"); - if (obj.Header == null) throw new MissingFieldException("Header cannot be null"); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Services/ObjectReader.cs rename to src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectStreamer.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Services/ObjectStreamer.cs rename to src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs rename to src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index 48a1275..fb7f987 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -93,7 +93,7 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) return new Checksum { Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(data.Sha256()) + Sum = ByteString.CopyFrom(data.Sha256()) }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index 6e2a5dc..dccb35f 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -1,4 +1,7 @@ +using System.Collections.Specialized; +using System.Linq; using System.Security.Cryptography; + using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ModelsV2; @@ -9,36 +12,38 @@ namespace FrostFS.SDK.ClientV2; public static class RequestConstructor { - public static void AddMetaHeader(this IRequest request, RequestMetaHeader? metaHeader = null) + public static void AddMetaHeader(this IRequest request, NameValueCollection? xHeaders, Session.SessionToken? sessionToken = null) { - if (request.MetaHeader is not null) return; - metaHeader ??= MetaHeader.Default().ToGrpcMessage(); - request.MetaHeader = metaHeader; + if (request.MetaHeader is not null) + return; + + request.MetaHeader = MetaHeader.Default().ToGrpcMessage(); + + if (sessionToken != null) + request.MetaHeader.SessionToken = sessionToken; + + if (xHeaders != null && xHeaders.Count > 0) + request.MetaHeader.XHeaders.AddRange( + xHeaders.Cast().SelectMany(key => xHeaders.GetValues(key), + (k, v) => new XHeader { Key = k, Value = v })); } - public static void AddObjectSessionToken( - this IRequest request, - Session.SessionToken sessionToken, - ContainerID cid, - ObjectID oid, + public static void CreateObjectTokenContext(this Session.SessionToken sessionToken, + Address address, ObjectSessionContext.Types.Verb verb, ECDsa key) { - if (request.MetaHeader.SessionToken is not null) - return; + ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId }; - request.MetaHeader.SessionToken = sessionToken; - var ctx = new ObjectSessionContext + if (address.ObjectId != null) + target.Objects.Add(address.ObjectId); + + sessionToken.Body.Object = new() { - Target = new ObjectSessionContext.Types.Target - { - Container = cid, - Objects = { oid } - }, + Target = target, Verb = verb }; - request.MetaHeader.SessionToken.Body.Object = ctx; - request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body); + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); } } diff --git a/src/FrostFS.SDK.ClientV2/Services/SearchReader.cs b/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Services/SearchReader.cs rename to src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 6e65c02..2b36a64 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -102,7 +102,7 @@ public static class Verifier var status = resp.MetaHeader.Status.ToModel(); if (!status.IsSuccess) - throw new ApplicationException(status.ToString()); + throw new ResponseException(status); } /// diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Context.cs index b1038ec..4ac90a8 100644 --- a/src/FrostFS.SDK.ModelsV2/Context.cs +++ b/src/FrostFS.SDK.ModelsV2/Context.cs @@ -12,8 +12,6 @@ public class Context() public CancellationToken CancellationToken { get; set; } = default; public TimeSpan Timeout { get; set; } = default; - public string SessionToken { get; set; } = string.Empty; - public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; public Action? Callback { get; set; } diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index be180b0..d4993b7 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -18,4 +18,9 @@ + + + <_Parameter1>FrostFS.SDK.ClientV2 + + diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index 4cbfaae..fa2586a 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -6,32 +6,56 @@ namespace FrostFS.SDK.ModelsV2; public class FrostFsObject { - public FrostFsObject(ObjectId objectId, ObjectHeader header, byte[] payload) + /// + /// Creates new instance from ObjectHeader + /// + /// + public FrostFsObject(ObjectHeader header) { - ObjectId = objectId; - Payload = payload; Header = header; } - public FrostFsObject(ContainerId container, byte[] payload, ObjectType objectType = ObjectType.Regular) + /// + /// Creates new instance with specified parameters + /// + /// + /// + public FrostFsObject(ContainerId container, ObjectType objectType = ObjectType.Regular) { - Payload = payload; - Header = new ObjectHeader(containerId: container, type: objectType, attributes: []); + Header = new ObjectHeader(containerId: container, type: objectType); } + /// + /// Header contains metadata for the object + /// + /// public ObjectHeader Header { get; set; } + /// + /// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed. + /// public ObjectId? ObjectId { get; internal set; } - - public byte[] Payload { get; set; } + /// + /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings + /// Used only for PutSingleObject method + /// + /// Buffer for output data + public byte[] Payload { get; set; } = []; + + /// + /// A payload is obtained via stream reader + /// + /// Reader for received data public IObjectReader? ObjectReader { get; set; } - public Signature? Signature { get; set; } - + /// + /// Applied only for the last Object in chain in case of manual multipart uploading + /// + /// Parent for multipart object public void SetParent(LargeObject largeObject) { if (Header?.Split == null) @@ -41,7 +65,7 @@ public class FrostFsObject } } -public class LargeObject(ContainerId container) : FrostFsObject(container, []) +public class LargeObject(ContainerId container) : FrostFsObject(container) { private readonly SHA256 payloadHash = SHA256.Create(); @@ -66,11 +90,11 @@ public class LargeObject(ContainerId container) : FrostFsObject(container, []) public class LinkObject : FrostFsObject { - public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId, []) + public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId) { Header!.Split = new Split(splitId) { - ParentHeader = largeObject.Header + ParentHeader = largeObject.Header }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs index 69a5a99..0a8d64c 100644 --- a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs +++ b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs @@ -1,14 +1 @@ -using System.IO; - namespace FrostFS.SDK.ModelsV2; - -public class PutObjectParameters -{ - public ObjectHeader? Header { get; set; } - - public Stream? Payload { get; set; } - - public bool ClientCut { get; set; } - - public int BufferMaxSize { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/SessionToken.cs index 6fde99e..e7da09a 100644 --- a/src/FrostFS.SDK.ModelsV2/SessionToken.cs +++ b/src/FrostFS.SDK.ModelsV2/SessionToken.cs @@ -1,8 +1,11 @@ namespace FrostFS.SDK.ModelsV2; -public class SessionToken(byte[] sessionKey, byte[] id) +public class SessionToken { - public byte[] Id { get; } = id; + internal SessionToken(byte[] token) + { + Token = token; + } - public byte[] SessionKey { get; } = sessionKey; + public byte[] Token { get; private set; } } diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 498a47a..9293579 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -8,10 +8,10 @@ using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.SDK.ModelsV2.Enums; using Google.Protobuf; using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Parameters; namespace FrostFS.SDK.Tests; - public abstract class ContainerTestsBase { protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; @@ -38,12 +38,12 @@ public abstract class ContainerTestsBase protected IFrostFSClient GetClient() { return ClientV2.Client.GetTestInstance( - Settings, - null, - new NetworkMocker(this.key).GetMock().Object, - new SessionMocker(this.key).GetMock().Object, - Mocker.GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); } } @@ -52,7 +52,9 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void CreateContainerTest() { - var result = await GetClient().CreateContainerAsync(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); + var param = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); + + var result = await GetClient().CreateContainerAsync(param); Assert.NotNull(result); Assert.NotNull(result.Value); @@ -66,7 +68,7 @@ public class ContainerTest : ContainerTestsBase Mocker.Acl = BasicAcl.PublicRO; - var result = await GetClient().GetContainerAsync(cid); + var result = await GetClient().GetContainerAsync(new PrmGetContainer(cid)); Assert.NotNull(result); Assert.Equal(Mocker.Acl, result.BasicAcl); @@ -99,9 +101,10 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void DeleteContainerAsyncTest() { + Mocker.ReturnContainerRemoved = true; var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - await GetClient().DeleteContainerAsync(cid); + await GetClient().DeleteContainerAsync(new PrmDeleteContainer(cid)); Assert.Single(Mocker.Requests); diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 2188795..aff566d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -77,6 +77,21 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) getResponse.VerifyHeader = GetResponseVerificationHeader(getResponse); + var getNoContainerResponse = new GetResponse + { + Body = new (), + MetaHeader = new ResponseMetaHeader + { + Status = new Status.Status + { + Code = 3072, + Message = "container not found" + } + } + }; + + getNoContainerResponse.VerifyHeader = GetResponseVerificationHeader(getNoContainerResponse); + mock.Setup(x => x.GetAsync( It.IsAny(), It.IsAny(), @@ -86,12 +101,22 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) { Verifier.CheckRequest(r); + if (ReturnContainerRemoved) + { + return new AsyncUnaryCall( + Task.FromResult(getNoContainerResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.NotFound, string.Empty), + () => ResponseMetaData, + () => { }); + } + return new AsyncUnaryCall( Task.FromResult(getResponse), Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, - () => { }); + () => { }); }); var listResponse = new ListResponse @@ -154,6 +179,8 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) return mock; } + public bool ReturnContainerRemoved { get; set; } + public List ContainerIds { get; set; } = []; public List> Requests { get; set; } = []; diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index d524284..2513908 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -2,6 +2,7 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2.Parameters; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; @@ -76,7 +77,7 @@ public class ObjectTest : ObjectTestsBase Timeout = TimeSpan.FromSeconds(2) }; - var result = await client.GetObjectAsync(ContainerId, objectId, context); + var result = await client.GetObjectAsync(new PrmGetObject(ContainerId, objectId) { Context = context }); Assert.NotNull(result); @@ -97,7 +98,7 @@ public class ObjectTest : ObjectTestsBase var bytes = new byte[1024]; rnd.NextBytes(bytes); - var param = new PutObjectParameters + var param = new PrmPutObject { Header = Mocker.ObjectHeader, Payload = new MemoryStream(bytes), @@ -132,7 +133,7 @@ public class ObjectTest : ObjectTestsBase byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); var fileLength = bytes.Length; - var param = new PutObjectParameters + var param = new PrmPutObject { Header = Mocker.ObjectHeader, Payload = new MemoryStream(bytes), @@ -196,7 +197,7 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - await GetClient().DeleteObjectAsync(ContainerId, Mocker.ObjectId); + await GetClient().DeleteObjectAsync(new PrmDeleteObject(ContainerId, Mocker.ObjectId)); var request = Mocker.DeleteRequests.FirstOrDefault(); Assert.NotNull(request); @@ -209,7 +210,7 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - var response = await GetClient().GetObjectHeadAsync(ContainerId, Mocker.ObjectId); + var response = await GetClient().GetObjectHeadAsync(new PrmGetObjectHead(ContainerId, Mocker.ObjectId)); var request = Mocker.HeadRequests.FirstOrDefault(); Assert.NotNull(request); diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index ad9f5ee..ba7e32c 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -4,24 +4,30 @@ using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.Cryptography; + using Grpc.Core; using Microsoft.Extensions.Options; using Grpc.Core.Interceptors; using System.Diagnostics; +using static FrostFS.Session.SessionToken.Types.Body; +using FrostFS.SDK.ClientV2.Parameters; + namespace FrostFS.SDK.SmokeTests; public class SmokeTests { + private static PrmWait lightWait = new (100, 1); private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; private readonly string url = "http://172.29.238.97:8080"; [Fact] public async void NetworkMapTest() { - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var result = await fsClient.GetNetmapSnapshotAsync(); + var result = await client.GetNetmapSnapshotAsync(); Assert.True(result.Epoch > 0); Assert.Single(result.NodeInfoCollection); @@ -38,9 +44,9 @@ public class SmokeTests [Fact] public async void NodeInfoTest() { - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var result = await fsClient.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); @@ -51,80 +57,74 @@ public class SmokeTests } [Fact] - public async void NodeInfo_Statictics_Test() + public async void NodeInfo_Statistics_Test() { var ctx = new Context { Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") }; - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var result = await fsClient.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(); } - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioTest(int objectSize) + [Fact] + public async void GetSessionTest() { - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); - await Cleanup(fsClient); + var token = await client.CreateSessionAsync(new PrmCreateSession(100)); - var containerId = await fsClient.CreateContainerAsync( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))), - new Context - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - } - ); + var session = new Session.SessionToken().Deserialize(token.Token); + + var ecdsaKey = this.key.LoadWif(); + var owner = OwnerId.FromKey(ecdsaKey); - var context = new Context - { - Timeout = TimeSpan.FromSeconds(10), - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }; + var ownerHash = Base58.Decode(owner.Value); + + Assert.NotNull(session); + Assert.Null(session.Body.Container); + Assert.Null(session.Body.Object); + Assert.Equal(16, session.Body.Id.Length); + Assert.Equal(100ul, session.Body.Lifetime.Exp); + Assert.Equal(ownerHash, session.Body.OwnerId.Value); + Assert.Equal(33, session.Body.SessionKey.Length); + Assert.Equal(ContextOneofCase.None, session.Body.ContextCase); + } - var container = await GetContainer(fsClient, containerId, context); + [Fact] + public async void CreateObjectWithSessionToken() + { + using var client = Client.GetInstance(GetOptions(this.key, this.url)); + + await Cleanup(client); - Assert.NotNull(container); + var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue)); - var bytes = GetRandomBytes(objectSize); + var createContainerParam = new PrmCreateContainer( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); - var param = new PutObjectParameters + createContainerParam.XHeaders.Add("key1", "value1"); + + var containerId = await client.CreateContainerAsync(createContainerParam); + + var bytes = GetRandomBytes(1024); + + var param = new PrmPutObject { Header = new ObjectHeader( containerId: containerId, type: ObjectType.Regular, new ObjectAttribute("fileName", "test")), Payload = new MemoryStream(bytes), - ClientCut = false + ClientCut = false, + SessionToken = token }; + + var objectId = await client.PutObjectAsync(param); - var objectId = await fsClient.PutObjectAsync(param, new Context - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }); - - var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) - { - hasObject = true; - - var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await fsClient.GetObjectAsync(containerId, objectId!); + var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -137,11 +137,177 @@ public class SmokeTests Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); - await Cleanup(fsClient); + await Cleanup(client); + } - await Task.Delay(2000); + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioTest(int objectSize) + { + using var client = Client.GetInstance(GetOptions(this.key, this.url)); + + await Cleanup(client); - await foreach (var _ in fsClient.ListContainersAsync()) + bool callbackInvoked = false; + var ctx = new Context + { + Timeout = TimeSpan.FromSeconds(20), + Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }) + }; + + var createContainerParam = new PrmCreateContainer( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + { + Context = ctx + }; + + var containerId = await client.CreateContainerAsync(createContainerParam); + + var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx)); + Assert.NotNull(container); + Assert.True(callbackInvoked); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmPutObject + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream(bytes), + ClientCut = false, + Context = new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + } + }; + + var objectId = await client.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter] })) + { + hasObject = true; + + var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId)); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + byte[]? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk); + } + + Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioWithSessionTest(int objectSize) + { + using var client = Client.GetInstance(GetOptions(this.key, this.url)); + + var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue)); + + await Cleanup(client); + + var ctx = new Context + { + Timeout = TimeSpan.FromSeconds(20), + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; + + var createContainerParam = new PrmCreateContainer( + new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + { + Context = ctx + }; + + var containerId = await client.CreateContainerAsync(createContainerParam); + + var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx)); + Assert.NotNull(container); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmPutObject + { + Header = new ObjectHeader( + containerId: containerId, + type: ObjectType.Regular, + new ObjectAttribute("fileName", "test")), + Payload = new MemoryStream(bytes), + ClientCut = false, + Context = new Context + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }, + SessionToken = token + }; + + var objectId = await client.PutObjectAsync(param); + + var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter], SessionToken = token })) + { + hasObject = true; + + var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId) { SessionToken = token }); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId) { SessionToken = token }); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + byte[]? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk); + } + + Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) { Assert.Fail("Containers exist"); } @@ -155,13 +321,16 @@ public class SmokeTests [InlineData(2 * 64 * 1024 * 1024 + 256)] public async void ClientCutScenarioTest(int objectSize) { - using var fsClient = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); - await Cleanup(fsClient); + await Cleanup(client); - var cnt = new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1))); + var createContainerParam = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + { + WaitParams = lightWait + }; - var containerId = await fsClient.CreateContainerAsync(cnt); + var containerId = await client.CreateContainerAsync(createContainerParam); var context = new Context { @@ -169,13 +338,13 @@ public class SmokeTests Interceptors = new([new MetricsInterceptor()]) }; - var container = await GetContainer(fsClient, containerId, context); + var container = await client.GetContainerAsync(new PrmGetContainer(containerId, context)); Assert.NotNull(container); byte[] bytes = GetRandomBytes(objectSize); - var param = new PutObjectParameters + var param = new PrmPutObject { Header = new ObjectHeader( containerId: containerId, @@ -185,16 +354,16 @@ public class SmokeTests ClientCut = true }; - var objectId = await fsClient.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param); var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in fsClient.SearchObjectsAsync(containerId, [filter])) + await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId, filter))) { hasObject = true; - var objHeader = await fsClient.GetObjectHeadAsync(containerId, objectId); + var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -203,7 +372,7 @@ public class SmokeTests Assert.True(hasObject); - var @object = await fsClient.GetObjectAsync(containerId, objectId!); + var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -216,14 +385,23 @@ public class SmokeTests Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); - await Cleanup(fsClient); + await Cleanup(client); - await Task.Delay(2000); + var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - await foreach (var _ in fsClient.ListContainersAsync()) + IAsyncEnumerator? enumerator = null; + do { - Assert.Fail("Containers exist"); + if (deadline <= DateTime.UtcNow) + { + Assert.Fail("Containers exist"); + break; + } + + enumerator = client.ListContainersAsync().GetAsyncEnumerator(); + await Task.Delay(500); } + while (await enumerator!.MoveNextAsync()); } private static byte[] GetRandomBytes(int size) @@ -243,36 +421,16 @@ public class SmokeTests }); } - static async Task Cleanup(IFrostFSClient fsClient) + static async Task Cleanup(IFrostFSClient client) { - await foreach (var cid in fsClient.ListContainersAsync()) + await foreach (var cid in client.ListContainersAsync()) { - await fsClient.DeleteContainerAsync(cid); - } - } - - static async Task GetContainer(IFrostFSClient fsClient, ContainerId id, Context ctx) - { - while (true) - { - try - { - await Task.Delay(100); - return await fsClient.GetContainerAsync(id, ctx); - } - catch (ApplicationException) - { - if (DateTime.UtcNow >= ctx.Deadline) - throw new TimeoutException(); - } - catch (RpcException) - { - throw; - } + await client.DeleteContainerAsync(new PrmDeleteContainer(cid) { WaitParams = lightWait }); } } } + public class MetricsInterceptor() : Interceptor { public override AsyncUnaryCall AsyncUnaryCall( From 816e1eb2f13c59456dcdaa09bfd1219d1a8cbbcd Mon Sep 17 00:00:00 2001 From: "p.gross" Date: Mon, 22 Jul 2024 14:18:42 +0300 Subject: [PATCH 11/65] [#17] Models: Remove internal visibility Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj | 5 ----- src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs | 2 +- src/FrostFS.SDK.ModelsV2/SessionToken.cs | 9 ++------- src/FrostFS.SDK.ModelsV2/Version.cs | 12 +++--------- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index d4993b7..be180b0 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -18,9 +18,4 @@ - - - <_Parameter1>FrostFS.SDK.ClientV2 - - diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index fa2586a..6b095a8 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -36,7 +36,7 @@ public class FrostFsObject /// public ObjectId? ObjectId { - get; internal set; + get; set; } /// diff --git a/src/FrostFS.SDK.ModelsV2/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/SessionToken.cs index e7da09a..2865ca3 100644 --- a/src/FrostFS.SDK.ModelsV2/SessionToken.cs +++ b/src/FrostFS.SDK.ModelsV2/SessionToken.cs @@ -1,11 +1,6 @@ namespace FrostFS.SDK.ModelsV2; -public class SessionToken +public class SessionToken(byte[] token) { - internal SessionToken(byte[] token) - { - Token = token; - } - - public byte[] Token { get; private set; } + public byte[] Token { get; private set; } = token; } diff --git a/src/FrostFS.SDK.ModelsV2/Version.cs b/src/FrostFS.SDK.ModelsV2/Version.cs index 3d852fb..88bdee2 100644 --- a/src/FrostFS.SDK.ModelsV2/Version.cs +++ b/src/FrostFS.SDK.ModelsV2/Version.cs @@ -1,15 +1,9 @@ namespace FrostFS.SDK.ModelsV2; -public class Version +public class Version(int major, int minor) { - public int Major { get; set; } - public int Minor { get; set; } - - public Version(int major, int minor) - { - Major = major; - Minor = minor; - } + public int Major { get; set; } = major; + public int Minor { get; set; } = minor; public bool IsSupported(Version version) { From 3206abc33e3e65a8a422c6ca6d90d72bea652bc3 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 25 Jul 2024 12:28:38 +0300 Subject: [PATCH 12/65] [#18] Client: Rename parameters Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 40 ++++++------- .../Interfaces/IFrostFSClient.cs | 28 ++++----- ...eateContainer.cs => PrmContainerCreate.cs} | 2 +- ...leteContainer.cs => PrmContainerDelete.cs} | 2 +- ...{PrmGetContainer.cs => PrmContainerGet.cs} | 2 +- ...ListContainer.cs => PrmContainerGetAll.cs} | 2 +- ...NetmapSnapshot.cs => PrmNetmapSnapshot.cs} | 2 +- ...tworkSettings.cs => PrmNetworkSettings.cs} | 2 +- .../{PrmGetNodeInfo.cs => PrmNodeInfo.cs} | 2 +- ...{PrmDeleteObject.cs => PrmObjectDelete.cs} | 2 +- .../{PrmGetObject.cs => PrmObjectGet.cs} | 2 +- ...rmGetObjectHead.cs => PrmObjectHeadGet.cs} | 2 +- .../{PrmPutObject.cs => PrmObjectPut.cs} | 2 +- ...{PrmSearchObject.cs => PrmObjectSearch.cs} | 2 +- ...rmCreateSession.cs => PrmSessionCreate.cs} | 2 +- ...tSingleObject.cs => PrmSingleObjectPut.cs} | 2 +- .../Services/ContainerServiceProvider.cs | 8 +-- .../Services/NetmapServiceProvider.cs | 4 +- .../Services/ObjectServiceProvider.cs | 28 ++++----- .../Services/SessionServiceProvider.cs | 2 +- .../Tools/RequestConstructor.cs | 7 ++- src/FrostFS.SDK.Cryptography/Extentions.cs | 1 + src/FrostFS.SDK.ModelsV2/Constants.cs | 7 --- src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs | 8 --- src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 25 ++++++++ src/FrostFS.SDK.ModelsV2/Misc/Constants.cs | 52 +++++++++++++++++ src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs | 31 ++++------ .../{ => Session}/SessionToken.cs | 0 src/FrostFS.SDK.Tests/ContainerTest.cs | 6 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 10 ++-- src/FrostFS.SDK.Tests/SmokeTests.cs | 57 ++++++++++--------- 31 files changed, 200 insertions(+), 142 deletions(-) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmCreateContainer.cs => PrmContainerCreate.cs} (90%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmDeleteContainer.cs => PrmContainerDelete.cs} (91%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetContainer.cs => PrmContainerGet.cs} (86%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmListContainer.cs => PrmContainerGetAll.cs} (87%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetNetmapSnapshot.cs => PrmNetmapSnapshot.cs} (78%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetNetworkSettings.cs => PrmNetworkSettings.cs} (78%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetNodeInfo.cs => PrmNodeInfo.cs} (80%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmDeleteObject.cs => PrmObjectDelete.cs} (89%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetObject.cs => PrmObjectGet.cs} (89%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmGetObjectHead.cs => PrmObjectHeadGet.cs} (89%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmPutObject.cs => PrmObjectPut.cs} (96%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmSearchObject.cs => PrmObjectSearch.cs} (91%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmCreateSession.cs => PrmSessionCreate.cs} (85%) rename src/FrostFS.SDK.ClientV2/Parameters/{PrmPutSingleObject.cs => PrmSingleObjectPut.cs} (89%) delete mode 100644 src/FrostFS.SDK.ModelsV2/Constants.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Misc/Constants.cs rename src/FrostFS.SDK.ModelsV2/{ => Session}/SessionToken.cs (100%) diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 6d09283..294726a 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -117,26 +117,26 @@ public class Client : IFrostFSClient } #region ContainerImplementation - public Task GetContainerAsync(PrmGetContainer args) + public Task GetContainerAsync(PrmContainerGet args) { var service = GetContainerService(args); return service.GetContainerAsync(args); } - public IAsyncEnumerable ListContainersAsync(PrmListContainer? args = null) + public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { - args = args ?? new PrmListContainer(); + args = args ?? new PrmContainerGetAll(); var service = GetContainerService(args); return service.ListContainersAsync(args); } - public Task CreateContainerAsync(PrmCreateContainer args) + public Task CreateContainerAsync(PrmContainerCreate args) { var service = GetContainerService(args); return service.CreateContainerAsync(args); } - public Task DeleteContainerAsync(PrmDeleteContainer args) + public Task DeleteContainerAsync(PrmContainerDelete args) { var service = GetContainerService(args); return service.DeleteContainerAsync(args); @@ -144,60 +144,60 @@ public class Client : IFrostFSClient #endregion #region NetworkImplementation - public Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args) + public Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args) { - args ??= new PrmGetNetmapSnapshot(); + args ??= new PrmNetmapSnapshot(); var service = GetNetmapService(args); return service.GetNetmapSnapshotAsync(args); } - public Task GetNodeInfoAsync(PrmGetNodeInfo? args) + public Task GetNodeInfoAsync(PrmNodeInfo? args) { - args ??= new PrmGetNodeInfo(); + args ??= new PrmNodeInfo(); var service = GetNetmapService(args); return service.GetLocalNodeInfoAsync(args); } - public Task GetNetworkSettingsAsync(PrmGetNetworkSettings? args) + public Task GetNetworkSettingsAsync(PrmNetworkSettings? args) { - args ??= new PrmGetNetworkSettings(); + args ??= new PrmNetworkSettings(); var service = GetNetmapService(args); return service.GetNetworkSettingsAsync(args.Context!); } #endregion #region ObjectImplementation - public Task GetObjectHeadAsync(PrmGetObjectHead args) + public Task GetObjectHeadAsync(PrmObjectHeadGet args) { var service = GetObjectService(args); return service.GetObjectHeadAsync(args); } - public Task GetObjectAsync(PrmGetObject args) + public Task GetObjectAsync(PrmObjectGet args) { var service = GetObjectService(args); return service.GetObjectAsync(args); } - public Task PutObjectAsync(PrmPutObject args) + public Task PutObjectAsync(PrmObjectPut args) { var service = GetObjectService(args); return service.PutObjectAsync(args); } - public Task PutSingleObjectAsync(PrmPutSingleObject args) + public Task PutSingleObjectAsync(PrmSingleObjectPut args) { var service = GetObjectService(args); return service.PutSingleObjectAsync(args); } - public Task DeleteObjectAsync(PrmDeleteObject args) + public Task DeleteObjectAsync(PrmObjectDelete args) { var service = GetObjectService(args); return service.DeleteObjectAsync(args); } - public IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args) + public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { var service = GetObjectService(args); return service.SearchObjectsAsync(args); @@ -205,7 +205,7 @@ public class Client : IFrostFSClient #endregion #region SessionImplementation - public async Task CreateSessionAsync(PrmCreateSession args) + public async Task CreateSessionAsync(PrmSessionCreate args) { var session = await CreateSessionInternalAsync(args); var token = session.Serialize(); @@ -213,7 +213,7 @@ public class Client : IFrostFSClient return new ModelsV2.SessionToken(token); } - internal Task CreateSessionInternalAsync(PrmCreateSession args) + internal Task CreateSessionInternalAsync(PrmSessionCreate args) { var service = GetSessionService(args); return service.CreateSessionAsync(args); @@ -229,7 +229,7 @@ public class Client : IFrostFSClient private async void CheckFrostFsVersionSupport(Context? ctx = default) { - var args = new PrmGetNodeInfo(ctx); + var args = new PrmNodeInfo(ctx); var service = GetNetmapService(args); var localNodeInfo = await service.GetLocalNodeInfoAsync(args); diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index a483abc..a3a7f81 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -9,39 +9,39 @@ namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable { #region Network - Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args = null); + Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null); - Task GetNodeInfoAsync(PrmGetNodeInfo? args = null); + Task GetNodeInfoAsync(PrmNodeInfo? args = null); - Task GetNetworkSettingsAsync(PrmGetNetworkSettings? args = null); + Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null); #endregion #region Session - Task CreateSessionAsync(PrmCreateSession args); + Task CreateSessionAsync(PrmSessionCreate args); #endregion #region Container - Task GetContainerAsync(PrmGetContainer args); + Task GetContainerAsync(PrmContainerGet args); - IAsyncEnumerable ListContainersAsync(PrmListContainer? args = null); + IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null); - Task CreateContainerAsync(PrmCreateContainer args); + Task CreateContainerAsync(PrmContainerCreate args); - Task DeleteContainerAsync(PrmDeleteContainer args); + Task DeleteContainerAsync(PrmContainerDelete args); #endregion #region Object - Task GetObjectHeadAsync(PrmGetObjectHead args); + Task GetObjectHeadAsync(PrmObjectHeadGet args); - Task GetObjectAsync(PrmGetObject args); + Task GetObjectAsync(PrmObjectGet args); - Task PutObjectAsync(PrmPutObject putObjectParameters); + Task PutObjectAsync(PrmObjectPut putObjectParameters); - Task PutSingleObjectAsync(PrmPutSingleObject args); + Task PutSingleObjectAsync(PrmSingleObjectPut args); - Task DeleteObjectAsync(PrmDeleteObject args); + Task DeleteObjectAsync(PrmObjectDelete args); - IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args); + IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); #endregion #region Tools diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs similarity index 90% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index 9209a03..4185a41 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateContainer.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmCreateContainer(ModelsV2.Container container) : IContext +public sealed class PrmContainerCreate(ModelsV2.Container container) : IContext { public ModelsV2.Container Container { get; set; } = container; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs index 4451b00..8455f51 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteContainer.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmDeleteContainer(ContainerId containerId, Context? ctx = null) : IContext +public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = null) : IContext { public ContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs similarity index 86% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs index 59350fe..2761ba2 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetContainer.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetContainer(ContainerId containerId, Context? ctx = null) : IContext +public sealed class PrmContainerGet(ContainerId containerId, Context? ctx = null) : IContext { public ContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index 3d4c1a2..03cb084 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmListContainer.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmListContainer() : IContext +public sealed class PrmContainerGetAll() : IContext { public string SessionToken { get; set; } = string.Empty; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs similarity index 78% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index e3e28c6..117ab85 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetNetmapSnapshot(Context? context = default) : IContext +public sealed class PrmNetmapSnapshot(Context? context = default) : IContext { /// /// FrostFS request X-Headers diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs similarity index 78% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs index 8db583e..250eb04 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetNetworkSettings(Context? context = default) : IContext +public sealed class PrmNetworkSettings(Context? context = default) : IContext { /// /// FrostFS request X-Headers diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs similarity index 80% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs index 78a7697..8a293a5 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetNodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetNodeInfo(Context? context = default) : IContext +public sealed class PrmNodeInfo(Context? context = default) : IContext { /// /// FrostFS request X-Headers diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs index 8241f64..100f558 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmDeleteObject.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmDeleteObject(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; public ObjectId ObjectId { get; set; } = objectId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs index 5ee1fd9..9c8cb1c 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObject.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetObject(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; public ObjectId ObjectId { get; set; } = objectId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index 88287a8..5184fe8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmGetObjectHead.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmGetObjectHead(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; public ObjectId ObjectId { get; set; } = objectId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index 7e4999d..219af3e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutObject.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -3,7 +3,7 @@ using System.IO; using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmPutObject : IContext, ISessionToken +public sealed class PrmObjectPut : IContext, ISessionToken { /// /// Need to provide values like ContainerId and ObjectType to create and object. diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index 8c0ad8c..9ac4f8d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSearchObject.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -3,7 +3,7 @@ using System.Collections.Specialized; using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmSearchObject(ContainerId containerId, params ObjectFilter[] filters) : IContext, ISessionToken +public sealed class PrmObjectSearch(ContainerId containerId, params ObjectFilter[] filters) : IContext, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs similarity index 85% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs index 870a481..22f0c4e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmCreateSession.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmCreateSession(ulong expiration, Context? context = default) : IContext +public sealed class PrmSessionCreate(ulong expiration, Context? context = default) : IContext { public ulong Expiration { get; set; } = expiration; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs rename to src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs index 95e514f..0d1df0f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmPutSingleObject.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs @@ -2,7 +2,7 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmPutSingleObject(FrostFsObject frostFsObject, Context? context = null) : IContext, ISessionToken +public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, Context? context = null) : IContext, ISessionToken { public FrostFsObject FrostFsObject { get; set; } = frostFsObject; diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index dfbd614..44cee16 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -23,7 +23,7 @@ internal class ContainerServiceProvider : ContextAccessor containerServiceClient = service; } - internal async Task GetContainerAsync(PrmGetContainer args) + internal async Task GetContainerAsync(PrmContainerGet args) { GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders); @@ -34,7 +34,7 @@ internal class ContainerServiceProvider : ContextAccessor return response.Body.Container.ToModel(); } - internal async IAsyncEnumerable ListContainersAsync(PrmListContainer args) + internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; @@ -59,7 +59,7 @@ internal class ContainerServiceProvider : ContextAccessor } } - internal async Task CreateContainerAsync(PrmCreateContainer args) + internal async Task CreateContainerAsync(PrmContainerCreate args) { var ctx = args.Context!; var grpcContainer = args.Container.ToGrpcMessage(); @@ -87,7 +87,7 @@ internal class ContainerServiceProvider : ContextAccessor return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); } - internal async Task DeleteContainerAsync(PrmDeleteContainer args) + internal async Task DeleteContainerAsync(PrmContainerDelete args) { var ctx = args.Context!; var request = new DeleteRequest diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 0d0a6d3..f4d239a 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -40,7 +40,7 @@ internal class NetmapServiceProvider : ContextAccessor return settings; } - internal async Task GetLocalNodeInfoAsync(PrmGetNodeInfo args) + internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; var request = new LocalNodeInfoRequest @@ -72,7 +72,7 @@ internal class NetmapServiceProvider : ContextAccessor return response; } - internal async Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot args) + internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 4d4cece..035faf7 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -21,7 +21,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { readonly ObjectTools tools = new(ctx); - internal async Task GetObjectHeadAsync(PrmGetObjectHead args) + internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; var request = new HeadRequest @@ -54,7 +54,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return response.Body.Header.Header.ToModel(); } - internal async Task GetObjectAsync(PrmGetObject args) + internal async Task GetObjectAsync(PrmObjectGet args) { var ctx = args.Context!; @@ -84,7 +84,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return await GetObject(request, ctx); } - internal async Task DeleteObjectAsync(PrmDeleteObject args) + internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; var request = new DeleteRequest @@ -114,7 +114,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Verifier.CheckResponse(response); } - internal async IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args) + internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { var ctx = args.Context!; var request = new SearchRequest @@ -148,7 +148,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } } - internal Task PutObjectAsync(PrmPutObject args) + internal Task PutObjectAsync(PrmObjectPut args) { if (args.Header == null) throw new ArgumentException("Value cannot be null", nameof(args.Header)); @@ -162,7 +162,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return PutStreamObject(args); } - internal async Task PutSingleObjectAsync(PrmPutSingleObject args) + internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) { var ctx = args.Context!; var grpcObject = tools.CreateObject(args.FrostFsObject); @@ -195,7 +195,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C static readonly AsyncLocal asyncLocalSession = new (); - private async Task PutClientCutObject(PrmPutObject args) + private async Task PutClientCutObject(PrmObjectPut args) { var ctx = args.Context!; var tokenRaw = await GetOrCreateSession(args, ctx); @@ -209,7 +209,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C FrostFsObject? currentObject; - var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmGetNetworkSettings(ctx)); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)); var objectSize = (int)networkSettings.MaxObjectSize; @@ -247,7 +247,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C if (largeObject.PayloadLength == fullLength) break; - objectId = await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx) { SessionToken = token }); + objectId = await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token }); sentObjectIds.Add(objectId!); } @@ -258,7 +258,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C currentObject.SetParent(largeObject); - var putSingleObjectParams = new PrmPutSingleObject(currentObject, ctx) { SessionToken = token }; + var putSingleObjectParams = new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token }; objectId = await PutSingleObjectAsync(putSingleObjectParams); @@ -269,17 +269,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C linkObject.Header.Attributes.Clear(); - _ = await PutSingleObjectAsync(new PrmPutSingleObject(linkObject, ctx){ SessionToken = token }); + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, ctx){ SessionToken = token }); return tools.CalculateObjectId(largeObject.Header); } currentObject.AddAttributes(args.Header!.Attributes); - return await PutSingleObjectAsync(new PrmPutSingleObject(currentObject, ctx)); + return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx)); } - private async Task PutStreamObject(PrmPutObject args) + private async Task PutStreamObject(PrmObjectPut args) { var ctx = args.Context!; var payload = args.Payload!; @@ -426,7 +426,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { if (args.SessionToken is null) { - return await Context.Client.CreateSessionInternalAsync(new PrmCreateSession(uint.MaxValue, ctx)); + return await Context.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)); } return new Session.SessionToken().Deserialize(args.SessionToken.Token); diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 64c6597..61ea93f 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -15,7 +15,7 @@ internal class SessionServiceProvider : ContextAccessor _sessionServiceClient = sessionServiceClient; } - internal async Task CreateSessionAsync(PrmCreateSession args) + internal async Task CreateSessionAsync(PrmSessionCreate args) { var request = new CreateRequest { diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index dccb35f..4e1de51 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -33,12 +33,15 @@ public static class RequestConstructor ObjectSessionContext.Types.Verb verb, ECDsa key) { + if (sessionToken.Body.Object?.Target != null) + return; + ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId }; if (address.ObjectId != null) target.Objects.Add(address.ObjectId); - - sessionToken.Body.Object = new() + + sessionToken.Body.Object = new() { Target = target, Verb = verb diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index 5584136..534fb7d 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -21,6 +21,7 @@ public static class Extentions var sha256 = SHA256.Create(); return sha256.ComputeHash(value); } + public static ByteString Sha256(this IMessage data) { return ByteString.CopyFrom(data.ToByteArray().Sha256()); diff --git a/src/FrostFS.SDK.ModelsV2/Constants.cs b/src/FrostFS.SDK.ModelsV2/Constants.cs deleted file mode 100644 index eeb2b67..0000000 --- a/src/FrostFS.SDK.ModelsV2/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FrostFS.SDK.ModelsV2; - -public class Constants -{ - public const int ObjectChunkSize = 3 * (1 << 20); - public const int Sha256HashLength = 32; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs b/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs deleted file mode 100644 index 71ac276..0000000 --- a/src/FrostFS.SDK.ModelsV2/GrpcCallInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK.ModelsV2; - -public class GrpcCallInfo(string methodName, long elapsedMicroSec, bool hasError) -{ - public string MethodName { get; set; } = methodName; - public long ElapsedTimeMicroSec { get; set; } = elapsedMicroSec; - public bool HasError { get; } = hasError; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs new file mode 100644 index 0000000..b62654c --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs @@ -0,0 +1,25 @@ +using System.Security.Cryptography; +using System.Text; + +namespace FrostFS.SDK.ModelsV2; + +public class CheckSum +{ + public byte[]? Hash { get; set; } + + public static byte[] GetHash(byte[] content) + { + var sha256 = SHA256.Create(); + return sha256.ComputeHash(content); + } + + public static CheckSum CreateCheckSum(byte[] content) + { + return new CheckSum { Hash = GetHash(content) }; + } + + public override string ToString() + { + return Encoding.UTF8.GetString(Hash); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs b/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs new file mode 100644 index 0000000..4b72aa1 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs @@ -0,0 +1,52 @@ +namespace FrostFS.SDK.ModelsV2; + +public class Constants +{ + public const int ObjectChunkSize = 3 * (1 << 20); + public const int Sha256HashLength = 32; + + // HeaderPrefix is a prefix of key to object header value or property. + public const string HeaderPrefix = "$Object:"; + + // FilterHeaderVersion is a filter key to "version" field of the object header. + public const string FilterHeaderVersion = HeaderPrefix + "version"; + + // FilterHeaderObjectID is a filter key to "object_id" field of the object. + public const string FilterHeaderObjectID = HeaderPrefix + "objectID"; + + // FilterHeaderContainerID is a filter key to "container_id" field of the object header. + public const string FilterHeaderContainerID = HeaderPrefix + "containerID"; + + // FilterHeaderOwnerID is a filter key to "owner_id" field of the object header. + public const string FilterHeaderOwnerID = HeaderPrefix + "ownerID"; + + // FilterHeaderCreationEpoch is a filter key to "creation_epoch" field of the object header. + public const string FilterHeaderCreationEpoch = HeaderPrefix + "creationEpoch"; + + // FilterHeaderPayloadLength is a filter key to "payload_length" field of the object header. + public const string FilterHeaderPayloadLength = HeaderPrefix + "payloadLength"; + + // FilterHeaderPayloadHash is a filter key to "payload_hash" field of the object header. + public const string FilterHeaderPayloadHash = HeaderPrefix + "payloadHash"; + + // FilterHeaderObjectType is a filter key to "object_type" field of the object header. + public const string FilterHeaderObjectType = HeaderPrefix + "objectType"; + + // FilterHeaderHomomorphicHash is a filter key to "homomorphic_hash" field of the object header. + public const string FilterHeaderHomomorphicHash = HeaderPrefix + "homomorphicHash"; + + // FilterHeaderParent is a filter key to "split.parent" field of the object header. + public const string FilterHeaderParent = HeaderPrefix + "split.parent"; + + // FilterHeaderSplitID is a filter key to "split.splitID" field of the object header. + public const string FilterHeaderSplitID = HeaderPrefix + "split.splitID"; + + // FilterHeaderECParent is a filter key to "ec.parent" field of the object header. + public const string FilterHeaderECParent = HeaderPrefix + "ec.parent"; + + // FilterPropertyRoot is a filter key to check if regular object is on top of split hierarchy. + public const string FilterHeaderRoot = HeaderPrefix + "ROOT"; + + // FilterPropertyPhy is a filter key to check if an object physically stored on a node. + public const string FilterHeaderPhy = HeaderPrefix + "PHY"; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs index 9433c04..8fa4704 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs @@ -4,25 +4,16 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2.Netmap; -public class NodeInfo +public class NodeInfo( + Version version, + NodeState state, + IReadOnlyCollection addresses, + IReadOnlyDictionary attributes, + ReadOnlyMemory publicKey) { - public NodeInfo( - Version version, - NodeState state, - IReadOnlyCollection addresses, - IReadOnlyDictionary attributes, - ReadOnlyMemory publicKey) - { - Version = version; - State = state; - Addresses = addresses; - Attributes = attributes; - PublicKey = publicKey; - } - - public NodeState State { get; private set; } - public Version Version { get; private set; } - public IReadOnlyCollection Addresses { get; private set; } - public IReadOnlyDictionary Attributes { get; private set; } - public ReadOnlyMemory PublicKey { get; private set; } + public NodeState State { get; private set; } = state; + public Version Version { get; private set; } = version; + public IReadOnlyCollection Addresses { get; private set; } = addresses; + public IReadOnlyDictionary Attributes { get; private set; } = attributes; + public ReadOnlyMemory PublicKey { get; private set; } = publicKey; } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/SessionToken.cs rename to src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 9293579..8fbdc2a 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -52,7 +52,7 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void CreateContainerTest() { - var param = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); + var param = new PrmContainerCreate(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); var result = await GetClient().CreateContainerAsync(param); @@ -68,7 +68,7 @@ public class ContainerTest : ContainerTestsBase Mocker.Acl = BasicAcl.PublicRO; - var result = await GetClient().GetContainerAsync(new PrmGetContainer(cid)); + var result = await GetClient().GetContainerAsync(new PrmContainerGet(cid)); Assert.NotNull(result); Assert.Equal(Mocker.Acl, result.BasicAcl); @@ -104,7 +104,7 @@ public class ContainerTest : ContainerTestsBase Mocker.ReturnContainerRemoved = true; var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - await GetClient().DeleteContainerAsync(new PrmDeleteContainer(cid)); + await GetClient().DeleteContainerAsync(new PrmContainerDelete(cid)); Assert.Single(Mocker.Requests); diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 2513908..05573ff 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -77,7 +77,7 @@ public class ObjectTest : ObjectTestsBase Timeout = TimeSpan.FromSeconds(2) }; - var result = await client.GetObjectAsync(new PrmGetObject(ContainerId, objectId) { Context = context }); + var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = context }); Assert.NotNull(result); @@ -98,7 +98,7 @@ public class ObjectTest : ObjectTestsBase var bytes = new byte[1024]; rnd.NextBytes(bytes); - var param = new PrmPutObject + var param = new PrmObjectPut { Header = Mocker.ObjectHeader, Payload = new MemoryStream(bytes), @@ -133,7 +133,7 @@ public class ObjectTest : ObjectTestsBase byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); var fileLength = bytes.Length; - var param = new PrmPutObject + var param = new PrmObjectPut { Header = Mocker.ObjectHeader, Payload = new MemoryStream(bytes), @@ -197,7 +197,7 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - await GetClient().DeleteObjectAsync(new PrmDeleteObject(ContainerId, Mocker.ObjectId)); + await GetClient().DeleteObjectAsync(new PrmObjectDelete(ContainerId, Mocker.ObjectId)); var request = Mocker.DeleteRequests.FirstOrDefault(); Assert.NotNull(request); @@ -210,7 +210,7 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - var response = await GetClient().GetObjectHeadAsync(new PrmGetObjectHead(ContainerId, Mocker.ObjectId)); + var response = await GetClient().GetObjectHeadAsync(new PrmObjectHeadGet(ContainerId, Mocker.ObjectId)); var request = Mocker.HeadRequests.FirstOrDefault(); Assert.NotNull(request); diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index ba7e32c..406e948 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -18,9 +18,9 @@ namespace FrostFS.SDK.SmokeTests; public class SmokeTests { - private static PrmWait lightWait = new (100, 1); + private static readonly PrmWait lightWait = new (100, 1); private readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - private readonly string url = "http://172.29.238.97:8080"; + private readonly string url = "http://172.23.32.4:8080"; [Fact] public async void NetworkMapTest() @@ -74,7 +74,7 @@ public class SmokeTests { using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var token = await client.CreateSessionAsync(new PrmCreateSession(100)); + var token = await client.CreateSessionAsync(new PrmSessionCreate(100)); var session = new Session.SessionToken().Deserialize(token.Token); @@ -100,9 +100,9 @@ public class SmokeTests await Cleanup(client); - var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue)); + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); - var createContainerParam = new PrmCreateContainer( + var createContainerParam = new PrmContainerCreate( new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); createContainerParam.XHeaders.Add("key1", "value1"); @@ -111,7 +111,7 @@ public class SmokeTests var bytes = GetRandomBytes(1024); - var param = new PrmPutObject + var param = new PrmObjectPut { Header = new ObjectHeader( containerId: containerId, @@ -124,7 +124,7 @@ public class SmokeTests var objectId = await client.PutObjectAsync(param); - var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -161,7 +161,7 @@ public class SmokeTests }) }; - var createContainerParam = new PrmCreateContainer( + var createContainerParam = new PrmContainerCreate( new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) { Context = ctx @@ -169,13 +169,13 @@ public class SmokeTests var containerId = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx)); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); - var param = new PrmPutObject + var param = new PrmObjectPut { Header = new ObjectHeader( containerId: containerId, @@ -194,11 +194,11 @@ public class SmokeTests var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter] })) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] })) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId)); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -207,7 +207,7 @@ public class SmokeTests Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -236,7 +236,7 @@ public class SmokeTests { using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var token = await client.CreateSessionAsync(new PrmCreateSession(int.MaxValue)); + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); await Cleanup(client); @@ -246,7 +246,7 @@ public class SmokeTests Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) }; - var createContainerParam = new PrmCreateContainer( + var createContainerParam = new PrmContainerCreate( new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) { Context = ctx @@ -254,12 +254,12 @@ public class SmokeTests var containerId = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmGetContainer(containerId,ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx)); Assert.NotNull(container); var bytes = GetRandomBytes(objectSize); - var param = new PrmPutObject + var param = new PrmObjectPut { Header = new ObjectHeader( containerId: containerId, @@ -279,11 +279,11 @@ public class SmokeTests var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId) { Filters = [filter], SessionToken = token })) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter], SessionToken = token })) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId) { SessionToken = token }); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId) { SessionToken = token }); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -292,7 +292,7 @@ public class SmokeTests Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId) { SessionToken = token }); + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId) { SessionToken = token }); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -325,7 +325,7 @@ public class SmokeTests await Cleanup(client); - var createContainerParam = new PrmCreateContainer(new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + var createContainerParam = new PrmContainerCreate(new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) { WaitParams = lightWait }; @@ -338,13 +338,13 @@ public class SmokeTests Interceptors = new([new MetricsInterceptor()]) }; - var container = await client.GetContainerAsync(new PrmGetContainer(containerId, context)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId, context)); Assert.NotNull(container); byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmPutObject + var param = new PrmObjectPut { Header = new ObjectHeader( containerId: containerId, @@ -359,11 +359,11 @@ public class SmokeTests var filter = new ObjectFilter(ObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmSearchObject(containerId, filter))) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmGetObjectHead(containerId, objectId)); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -372,7 +372,7 @@ public class SmokeTests Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmGetObject(containerId, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -425,7 +425,7 @@ public class SmokeTests { await foreach (var cid in client.ListContainersAsync()) { - await client.DeleteContainerAsync(new PrmDeleteContainer(cid) { WaitParams = lightWait }); + await client.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }); } } } @@ -457,7 +457,8 @@ public class MetricsInterceptor() : Interceptor watch.Stop(); - var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + // Do somethins with call info + // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; return response; } From 35fe79140615dc463eab4a2aec944c8742351d98 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 25 Jul 2024 14:20:14 +0300 Subject: [PATCH 13/65] [#19] Client: Use specific classes for search Signed-off-by: Pavel Gross --- .../Mappers/Object/ObjectFilterMapper.cs | 4 +- .../Mappers/Object/ObjectHeaderMapper.cs | 4 +- .../Parameters/PrmContainerGetAll.cs | 2 - .../Parameters/PrmObjectSearch.cs | 8 +- .../Services/ContainerServiceProvider.cs | 48 +++---- .../Services/ObjectServiceProvider.cs | 27 ++-- .../Shared}/ContextAccessor.cs | 0 .../Services/Shared/SessionProvider.cs | 23 ++++ src/FrostFS.SDK.ClientV2/Tools/Object.cs | 2 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 4 +- src/FrostFS.SDK.Cryptography/UUID.cs | 47 +++++-- .../{ => Client}/ClientSettings.cs | 0 .../{ => Client}/Context.cs | 0 .../{ => Enums}/SignatureScheme.cs | 0 .../{ => Misc}/CallStatistics.cs | 0 src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 5 +- .../{ => Netmap}/Version.cs | 0 .../Object/FrostFsObject.cs | 13 -- .../Object/ObjectFilter.cs | 125 ++++++++++++++---- .../{ => Object}/OwnerId.cs | 5 + .../{ => Object}/SplitId.cs | 8 +- .../{ => Object}/Splitter.cs | 0 .../PutObjectParameters.cs | 1 - .../{ => Response}/MetaHeader.cs | 0 .../{ => Response}/Signature.cs | 0 .../{ => Response}/Status.cs | 0 src/FrostFS.SDK.Tests/SmokeTests.cs | 117 +++++++++++++--- 27 files changed, 320 insertions(+), 123 deletions(-) rename src/FrostFS.SDK.ClientV2/{Tools => Services/Shared}/ContextAccessor.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs rename src/FrostFS.SDK.ModelsV2/{ => Client}/ClientSettings.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Client}/Context.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Enums}/SignatureScheme.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Misc}/CallStatistics.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Netmap}/Version.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Object}/OwnerId.cs (83%) rename src/FrostFS.SDK.ModelsV2/{ => Object}/SplitId.cs (89%) rename src/FrostFS.SDK.ModelsV2/{ => Object}/Splitter.cs (100%) delete mode 100644 src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs rename src/FrostFS.SDK.ModelsV2/{ => Response}/MetaHeader.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Response}/Signature.cs (100%) rename src/FrostFS.SDK.ModelsV2/{ => Response}/Status.cs (100%) diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs index ee819c6..2d9fed7 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -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() }; } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 2292811..12ff85c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -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; - } + } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index 03cb084..77d954e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -4,8 +4,6 @@ namespace FrostFS.SDK.ClientV2.Parameters; public sealed class PrmContainerGetAll() : IContext { - public string SessionToken { get; set; } = string.Empty; - /// /// FrostFS request X-Headers /// diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index 9ac4f8d..c4e8fc9 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -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 { + /// + /// Defines container for the search + /// + /// public ContainerId ContainerId { get; set; } = containerId; /// /// Defines the search criteria /// /// Collection of filters - public IEnumerable Filters { get; set; } = filters; + public IEnumerable Filters { get; set; } = filters; /// /// FrostFS request X-Headers diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 44cee16..2a15828 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -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 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 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); } } } } - - diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 035faf7..1c8bf2f 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -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 GetOrCreateSession(ISessionToken args, Context ctx) + { + return await sessions.GetOrCreateSession(args, ctx); + } internal async Task 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 asyncLocalSession = new (); private async Task 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 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); - } } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Tools/ContextAccessor.cs rename to src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs new file mode 100644 index 0000000..05e5723 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -0,0 +1,23 @@ + +using System.Threading.Tasks; +using FrostFS.SDK.ClientV2.Parameters; + +namespace FrostFS.SDK.ClientV2; + +internal interface ISessionProvider +{ + ValueTask GetOrCreateSession(ISessionToken args, Context ctx); +} + +internal class SessionProvider(ClientEnvironment env) +{ + public async ValueTask 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); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs index 1bc66a3..0dd931e 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -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; diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index fb7f987..f8d2c33 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -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; diff --git a/src/FrostFS.SDK.Cryptography/UUID.cs b/src/FrostFS.SDK.Cryptography/UUID.cs index 656acaf..fee2df9 100644 --- a/src/FrostFS.SDK.Cryptography/UUID.cs +++ b/src/FrostFS.SDK.Cryptography/UUID.cs @@ -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); } + /// + /// Serializes Guid to binary representation in direct order bytes format + /// + /// + /// 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] + ]; } } diff --git a/src/FrostFS.SDK.ModelsV2/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/ClientSettings.cs rename to src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs diff --git a/src/FrostFS.SDK.ModelsV2/Context.cs b/src/FrostFS.SDK.ModelsV2/Client/Context.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/Context.cs rename to src/FrostFS.SDK.ModelsV2/Client/Context.cs diff --git a/src/FrostFS.SDK.ModelsV2/SignatureScheme.cs b/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/SignatureScheme.cs rename to src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs diff --git a/src/FrostFS.SDK.ModelsV2/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/CallStatistics.cs rename to src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs index b62654c..6b5526a 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs @@ -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("-", ""); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Version.cs b/src/FrostFS.SDK.ModelsV2/Netmap/Version.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/Version.cs rename to src/FrostFS.SDK.ModelsV2/Netmap/Version.cs diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index 6b095a8..6ca2c22 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -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; } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs index 5fdb54f..fbed30c 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs @@ -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(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(); } } + +/// +/// Creates filter to search by Attribute +/// +/// Match type +/// Attribute key +/// Attribute value +public class FilterByAttribute(ObjectMatchType matchType, string key, string value) : ObjectFilter(matchType, key, value) { } + +/// +/// Creates filter to search by ObjectId +/// +/// Match type +/// ObjectId +public class FilterByObjectId(ObjectMatchType matchType, ObjectId objectId) : ObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } + +/// +/// Creates filter to search by OwnerId +/// +/// Match type +/// ObjectId +public class FilterByOwnerId(ObjectMatchType matchType, OwnerId ownerId) : ObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) {} + +/// +/// Creates filter to search by Version +/// +/// Match type +/// Version +public class FilterByVersion(ObjectMatchType matchType, Version version) : ObjectFilter(matchType, Constants.FilterHeaderVersion, version) {} + +/// +/// Creates filter to search by ContainerId +/// +/// Match type +/// ContainerId +public class FilterByContainerId(ObjectMatchType matchType, ContainerId containerId) : ObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) {} + +/// +/// Creates filter to search by creation Epoch +/// +/// Match type +/// Creation Epoch +public class FilterByEpoch(ObjectMatchType matchType, ulong epoch) : ObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) {} + +/// +/// Creates filter to search by Payload Length +/// +/// Match type +/// Payload Length +public class FilterByPayloadLength(ObjectMatchType matchType, ulong payloadLength) : ObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) {} + +/// +/// Creates filter to search by Payload Hash +/// +/// Match type +/// Payload Hash +public class FilterByPayloadHash(ObjectMatchType matchType, CheckSum payloadHash) : ObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) {} + +/// +/// Creates filter to search by Parent +/// +/// Match type +/// Parent +public class FilterByParent(ObjectMatchType matchType, ObjectId parentId) : ObjectFilter(matchType, Constants.FilterHeaderParent, parentId) {} + +/// +/// Creates filter to search by SplitId +/// +/// Match type +/// SplitId +public class FilterBySplitId(ObjectMatchType matchType, SplitId splitId) : ObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) {} + +/// +/// Creates filter to search by Payload Hash +/// +/// Match type +/// Payload Hash +public class FilterByECParent(ObjectMatchType matchType, ObjectId ecParentId) : ObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) {} + +/// +/// Creates filter to search Root objects +/// +public class FilterByRootObject() : ObjectFilter(ObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) {} + +/// +/// Creates filter to search objects that are physically stored on the server +/// (ObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) {} + diff --git a/src/FrostFS.SDK.ModelsV2/OwnerId.cs b/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs similarity index 83% rename from src/FrostFS.SDK.ModelsV2/OwnerId.cs rename to src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs index 8746572..b930bb7 100644 --- a/src/FrostFS.SDK.ModelsV2/OwnerId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs @@ -17,4 +17,9 @@ public class OwnerId(string id) { return Base58.Decode(Value); } + + public override string ToString() + { + return Value; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/SplitId.cs b/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs similarity index 89% rename from src/FrostFS.SDK.ModelsV2/SplitId.cs rename to src/FrostFS.SDK.ModelsV2/Object/SplitId.cs index eddbd14..111f632 100644 --- a/src/FrostFS.SDK.ModelsV2/SplitId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs @@ -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(); } } diff --git a/src/FrostFS.SDK.ModelsV2/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/Splitter.cs rename to src/FrostFS.SDK.ModelsV2/Object/Splitter.cs diff --git a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs b/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs deleted file mode 100644 index 0a8d64c..0000000 --- a/src/FrostFS.SDK.ModelsV2/PutObjectParameters.cs +++ /dev/null @@ -1 +0,0 @@ -namespace FrostFS.SDK.ModelsV2; diff --git a/src/FrostFS.SDK.ModelsV2/MetaHeader.cs b/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/MetaHeader.cs rename to src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs diff --git a/src/FrostFS.SDK.ModelsV2/Signature.cs b/src/FrostFS.SDK.ModelsV2/Response/Signature.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/Signature.cs rename to src/FrostFS.SDK.ModelsV2/Response/Signature.cs diff --git a/src/FrostFS.SDK.ModelsV2/Status.cs b/src/FrostFS.SDK.ModelsV2/Response/Status.cs similarity index 100% rename from src/FrostFS.SDK.ModelsV2/Status.cs rename to src/FrostFS.SDK.ModelsV2/Response/Status.cs diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index 406e948..f827677 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -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? 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()); } From 0ddde467cdb734a6f77cda2a18258752347e891f Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 1 Aug 2024 16:17:36 +0300 Subject: [PATCH 14/65] [#20] Client: Optimize memory usage Avoid memory allocation, use cache and static Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Cache.cs | 25 ++ src/FrostFS.SDK.ClientV2/Client.cs | 8 +- src/FrostFS.SDK.ClientV2/CllientKey.cs | 13 + .../Exceptions/ResponseException.cs | 13 +- .../FrostFS.SDK.ClientV2.csproj | 3 + src/FrostFS.SDK.ClientV2/Mappers/Container.cs | 4 +- .../Mappers/ContainerId.cs | 21 +- .../Mappers/MetaHeader.cs | 12 +- .../Mappers/Netmap/Netmap.cs | 4 +- .../Mappers/Netmap/PlacementPolicy.cs | 5 +- .../Mappers/Netmap/Replica.cs | 2 +- .../Mappers/Object/ObjectAttributeMapper.cs | 2 +- .../Mappers/Object/ObjectFilterMapper.cs | 2 +- .../Mappers/Object/ObjectHeaderMapper.cs | 17 +- .../Mappers/Object/ObjectId.cs | 2 +- src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs | 33 +- .../Mappers/SignatureMapper.cs | 5 +- src/FrostFS.SDK.ClientV2/Mappers/Status.cs | 8 +- src/FrostFS.SDK.ClientV2/Mappers/Version.cs | 68 +++- .../Parameters/PrmObjectPut.cs | 6 + .../Services/ContainerServiceProvider.cs | 24 +- .../Services/NetmapServiceProvider.cs | 6 +- .../Services/ObjectServiceProvider.cs | 301 ++++++++++-------- .../Services/SessionServiceProvider.cs | 4 +- .../Tools/ClientEnvironment.cs | 7 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 92 +++--- .../Tools/RequestConstructor.cs | 2 +- .../Tools/RequestSigner.cs | 2 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 5 +- src/FrostFS.SDK.Cryptography/Base58.cs | 8 +- src/FrostFS.SDK.Cryptography/Extentions.cs | 45 ++- src/FrostFS.SDK.Cryptography/Key.cs | 5 +- src/FrostFS.SDK.Cryptography/Murmur3_128.cs | 2 +- src/FrostFS.SDK.Cryptography/Range.cs | 14 +- .../FrostFS.SDK.ModelsV2.csproj | 1 + src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 9 +- .../Object/FrostFsObject.cs | 10 +- src/FrostFS.SDK.ModelsV2/Object/Splitter.cs | 3 + .../Response/{Status.cs => ResponseStatus.cs} | 2 +- src/FrostFS.SDK.Tests/ContainerTest.cs | 2 +- .../Mocks/AsyncStreamReaderMock.cs | 8 +- .../ContainerServiceBase.cs | 4 +- .../ContainerServiceMocks/GetContainerMock.cs | 6 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 16 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 119 ++++--- src/FrostFS.SDK.Tests/SmokeTests.cs | 18 +- 46 files changed, 596 insertions(+), 372 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Cache.cs create mode 100644 src/FrostFS.SDK.ClientV2/CllientKey.cs rename src/FrostFS.SDK.ModelsV2/Response/{Status.cs => ResponseStatus.cs} (83%) diff --git a/src/FrostFS.SDK.ClientV2/Cache.cs b/src/FrostFS.SDK.ClientV2/Cache.cs new file mode 100644 index 0000000..a41c8b9 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Cache.cs @@ -0,0 +1,25 @@ + + +using Microsoft.Extensions.Caching.Memory; + +namespace FrostFS.SDK.ClientV2 +{ + internal static class Cache + { + private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions + { + // TODO: get from options? + SizeLimit = 256 + }); + + private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions + { + // TODO: get from options? + SizeLimit = 1024 + }); + + internal static IMemoryCache Owners => _ownersCache; + + internal static IMemoryCache Containers => _containersCache; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 294726a..faf1802 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -125,7 +125,7 @@ public class Client : IFrostFSClient public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { - args = args ?? new PrmContainerGetAll(); + args ??= new PrmContainerGetAll(); var service = GetContainerService(args); return service.ListContainersAsync(args); } @@ -223,7 +223,10 @@ public class Client : IFrostFSClient #region ToolsImplementation public ObjectId CalculateObjectId(ObjectHeader header) { - return new ObjectTools(ClientCtx).CalculateObjectId(header); + if (header == null) + throw new ArgumentNullException(nameof(header)); + + return ObjectTools.CalculateObjectId(header, ClientCtx); } #endregion @@ -236,7 +239,6 @@ public class Client : IFrostFSClient if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; - Console.WriteLine(msg); throw new ApplicationException(msg); } } diff --git a/src/FrostFS.SDK.ClientV2/CllientKey.cs b/src/FrostFS.SDK.ClientV2/CllientKey.cs new file mode 100644 index 0000000..e415a98 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/CllientKey.cs @@ -0,0 +1,13 @@ +using FrostFS.SDK.Cryptography; +using Google.Protobuf; +using System.Security.Cryptography; + +namespace FrostFS.SDK.ClientV2 +{ + public class ClientKey(ECDsa key) + { + internal ECDsa ECDsaKey { get; } = key; + + internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey()); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs index 7772e1e..d72f173 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs +++ b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs @@ -1,12 +1,9 @@ -using System; using FrostFS.SDK.ModelsV2; +using System; -public class ResponseException : Exception +namespace FrostFS.SDK.ClientV2; + +public class ResponseException(ResponseStatus status) : Exception() { - public Status Status { get; set; } - - public ResponseException(Status status) : base() - { - Status = status; - } + public ResponseStatus Status { get; set; } = status; } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index 818bce2..f811e53 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -12,8 +12,11 @@ + + + diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs index 3d1207f..83bfb32 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs @@ -10,12 +10,12 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ContainerMapper { - public static Container.Container ToGrpcMessage(this ModelsV2.Container container) + public static Container.Container ToMessage(this ModelsV2.Container container) { return new Container.Container { BasicAcl = (uint)container.BasicAcl, - PlacementPolicy = container.PlacementPolicy.ToGrpcMessage(), + PlacementPolicy = container.PlacementPolicy.ToMessage(), Nonce = ByteString.CopyFrom(container.Nonce.ToBytes()) }; } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index 920c49a..ac9634d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -2,16 +2,29 @@ using FrostFS.Refs; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; +using Microsoft.Extensions.Caching.Memory; +using System; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ContainerIdMapper { - public static ContainerID ToGrpcMessage(this ContainerId containerId) + private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions() + .SetSlidingExpiration(TimeSpan.FromHours(1)) + .SetSize(1); + + public static ContainerID ToMessage(this ContainerId model) { - return new ContainerID + if (!Cache.Owners.TryGetValue(model, out ContainerID? message)) { - Value = ByteString.CopyFrom(Base58.Decode(containerId.Value)) - }; + message = new ContainerID + { + Value = ByteString.CopyFrom(Base58.Decode(model.Value)) + }; + + Cache.Owners.Set(model, message, _oneHourExpiration); + } + + return message!; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs index d3bf04f..360ddd4 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs @@ -1,23 +1,17 @@ using FrostFS.SDK.ModelsV2; using FrostFS.Session; -using Version = FrostFS.Refs.Version; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class MetaHeaderMapper { - public static RequestMetaHeader ToGrpcMessage(this MetaHeader metaHeader) + public static RequestMetaHeader ToMessage(this MetaHeader metaHeader) { return new RequestMetaHeader { - Version = new Version - { - Major = (uint)metaHeader.Version.Major, - Minor = (uint)metaHeader.Version.Minor, - - }, + Version = metaHeader.Version.ToMessage(), Epoch = (uint)metaHeader.Epoch, Ttl = (uint)metaHeader.Ttl }; - } + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs index 359041c..7d44809 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs @@ -10,6 +10,8 @@ public static class NetmapMapper { return new NetmapSnapshot( netmap.Body.Netmap.Epoch, - netmap.Body.Netmap.Nodes.Select(n => n.ToModel(netmap.MetaHeader.Version)).ToArray()); + netmap.Body.Netmap.Nodes + .Select(n => n.ToModel(netmap.MetaHeader.Version)) + .ToArray()); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs index a4cbb0d..47b51fa 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs @@ -5,7 +5,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; public static class PlacementPolicyMapper { - public static PlacementPolicy ToGrpcMessage(this ModelsV2.Netmap.PlacementPolicy placementPolicy) + public static PlacementPolicy ToMessage(this ModelsV2.Netmap.PlacementPolicy placementPolicy) { var pp = new PlacementPolicy { @@ -14,9 +14,10 @@ public static class PlacementPolicyMapper Replicas = { }, Unique = placementPolicy.Unique }; + foreach (var replica in placementPolicy.Replicas) { - pp.Replicas.Add(replica.ToGrpcMessage()); + pp.Replicas.Add(replica.ToMessage()); } return pp; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs index 7435085..215a127 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs @@ -4,7 +4,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; public static class ReplicaMapper { - public static Replica ToGrpcMessage(this ModelsV2.Netmap.Replica replica) + public static Replica ToMessage(this ModelsV2.Netmap.Replica replica) { return new Replica { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs index 75c4bb8..7528152 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs @@ -5,7 +5,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectAttributeMapper { - public static Header.Types.Attribute ToGrpcMessage(this ObjectAttribute attribute) + public static Header.Types.Attribute ToMessage(this ObjectAttribute attribute) { return new Header.Types.Attribute { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs index 2d9fed7..8353f5f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -7,7 +7,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectFilterMapper { - public static SearchRequest.Types.Body.Types.Filter ToGrpcMessage(this IObjectFilter filter) + public static SearchRequest.Types.Body.Types.Filter ToMessage(this IObjectFilter filter) { var objMatchTypeName = filter.MatchType switch { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 12ff85c..282e42e 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -10,7 +10,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectHeaderMapper { - public static Header ToGrpcMessage(this ObjectHeader header) + public static Header ToMessage(this ObjectHeader header) { var objTypeName = header.ObjectType switch { @@ -22,14 +22,16 @@ public static class ObjectHeaderMapper var head = new Header { - ContainerId = header.ContainerId.ToGrpcMessage(), + OwnerId = header!.OwnerId != null ? header.OwnerId.ToMessage() : null, + Version = header!.Version != null ? header.Version.ToMessage() : null, + ContainerId = header.ContainerId.ToMessage(), ObjectType = objTypeName, PayloadLength = header.PayloadLength }; foreach (var attribute in header.Attributes) { - head.Attributes.Add(attribute.ToGrpcMessage()); + head.Attributes.Add(attribute.ToMessage()); } var split = header.Split; @@ -37,17 +39,10 @@ public static class ObjectHeaderMapper { head.Split = new Header.Types.Split { - Parent = split.Parent?.ToGrpcMessage(), - ParentSignature = split.ParentSignature?.ToGrpcMessage(), - ParentHeader = split.ParentHeader?.ToGrpcMessage(), - Previous = split.Previous?.ToGrpcMessage(), SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null }; - - if (split.Children != null && split.Children.Count != 0) - head.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); } - + return head; } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs index bfd568d..c9afe0c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs @@ -6,7 +6,7 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectIdMapper { - public static ObjectID ToGrpcMessage(this ObjectId objectId) + public static ObjectID ToMessage(this ObjectId objectId) { return new ObjectID { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs index b4f332e..de8db49 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs @@ -1,21 +1,42 @@ using FrostFS.Refs; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using Google.Protobuf; +using Microsoft.Extensions.Caching.Memory; +using System; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class OwnerIdMapper { - public static OwnerID ToGrpcMessage(this OwnerId ownerId) + private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions() + .SetSlidingExpiration(TimeSpan.FromHours(1)) + .SetSize(1); + + public static OwnerID ToMessage(this OwnerId model) { - return new OwnerID + if (!Cache.Owners.TryGetValue(model, out OwnerID? message)) { - Value = ByteString.CopyFrom(ownerId.ToHash()) - }; + message = new OwnerID + { + Value = ByteString.CopyFrom(model.ToHash()) + }; + + Cache.Owners.Set(model, message, _oneHourExpiration); + } + + return message!; } - public static OwnerId ToModel(this OwnerID ownerId) + public static OwnerId ToModel(this OwnerID message) { - return new OwnerId(ownerId.ToString()); + if (!Cache.Owners.TryGetValue(message, out OwnerId? model)) + { + model = new OwnerId(Base58.Encode(message.Value.ToByteArray())); + + Cache.Owners.Set(message, model, _oneHourExpiration); + } + + return model!; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs index 2da358a..4343d4d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs @@ -6,14 +6,15 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class SignatureMapper { - public static Refs.Signature ToGrpcMessage(this Signature signature) + public static Refs.Signature ToMessage(this Signature signature) { var scheme = signature.Scheme switch { SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, - _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", paramName: nameof(signature.Scheme)) + _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", + paramName: nameof(signature.Scheme)) }; return new Refs.Signature diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs index 9e3d2eb..d489239 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs @@ -5,13 +5,15 @@ namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class StatusMapper { - public static ModelsV2.Status ToModel(this Status.Status status) + public static ModelsV2.ResponseStatus ToModel(this Status.Status status) { - if (status is null) return new ModelsV2.Status(StatusCode.Success); + if (status is null) + return new ModelsV2.ResponseStatus(StatusCode.Success); + var codeName = Enum.GetName(typeof(StatusCode), status.Code); return codeName is null ? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.") - : new ModelsV2.Status((StatusCode)Enum.Parse(typeof(StatusCode), codeName), status.Message); + : new ModelsV2.ResponseStatus((StatusCode)Enum.Parse(typeof(StatusCode), codeName), status.Message); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs index 549ff1a..df2066c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs @@ -1,20 +1,74 @@ +using System.Collections; +using System.Threading; using Version = FrostFS.Refs.Version; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class VersionMapper { - public static Version ToGrpcMessage(this ModelsV2.Version version) + private static readonly Hashtable _cacheMessages = []; + private static readonly Hashtable _cacheModels = []; + private static SpinLock _spinlock = new(); + + public static Version ToMessage(this ModelsV2.Version model) { - return new Version + var key = model.Major << 16 + model.Minor; + + if (!_cacheMessages.ContainsKey(key)) { - Major = (uint)version.Major, - Minor = (uint)version.Minor - }; + bool lockTaken = false; + try + { + _spinlock.Enter(ref lockTaken); + var message = new Version + { + Major = (uint)model.Major, + Minor = (uint)model.Minor + }; + + _cacheMessages.Add(key, message); + return message; + } + catch (System.ArgumentException) + { + // ignore attempt to add duplicate error. + } + finally + { + if (lockTaken) + _spinlock.Exit(false); + } + } + + return (Version)_cacheMessages[key]; } - public static ModelsV2.Version ToModel(this Version version) + public static ModelsV2.Version ToModel(this Version message) { - return new ModelsV2.Version((int)version.Major, (int)version.Minor); + var key = (int)message.Major << 16 + (int)message.Minor; + + if (!_cacheModels.ContainsKey(key)) + { + bool lockTaken = false; + try + { + _spinlock.Enter(ref lockTaken); + var model = new ModelsV2.Version((int)message.Major, (int)message.Minor); + + _cacheModels.Add(key, model); + return model; + } + catch (System.ArgumentException) + { + // ignore attempt to add duplicate error. + } + finally + { + if (lockTaken) + _spinlock.Exit(false); + } + } + + return (ModelsV2.Version)_cacheModels[key]; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index 219af3e..cd859d7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -41,4 +41,10 @@ public sealed class PrmObjectPut : IContext, ISessionToken /// public SessionToken? SessionToken { get; set; } + + internal int MaxObjectSizeCache { get; set; } + + internal ulong CurrentStreamPosition { get; set; } = 0; + + internal ulong FullLength { get; set; } = 0; } diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 2a15828..7f412c1 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -17,7 +17,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { internal async Task GetContainerAsync(PrmContainerGet args) { - GetRequest request = GetContainerRequest(args.ContainerId.ToGrpcMessage(), args.XHeaders); + GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders); var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken); @@ -34,12 +34,12 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { Body = new ListRequest.Types.Body { - OwnerId = Context.Owner.ToGrpcMessage() + OwnerId = Context.Owner.ToMessage() } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -54,21 +54,21 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient internal async Task CreateContainerAsync(PrmContainerCreate args) { var ctx = args.Context!; - var grpcContainer = args.Container.ToGrpcMessage(); - grpcContainer.OwnerId = Context.Owner.ToGrpcMessage(); - grpcContainer.Version = Context.Version.ToGrpcMessage(); + var grpcContainer = args.Container.ToMessage(); + grpcContainer.OwnerId = Context.Owner.ToMessage(); + grpcContainer.Version = Context.Version.ToMessage(); var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = Context.Key.SignRFC6979(grpcContainer) + Signature = Context.Key.ECDsaKey.SignRFC6979(grpcContainer) } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -86,14 +86,14 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { Body = new DeleteRequest.Types.Body { - ContainerId = args.ContainerId.ToGrpcMessage(), - Signature = Context.Key.SignRFC6979(args.ContainerId.ToGrpcMessage().Value) + ContainerId = args.ContainerId.ToMessage(), + Signature = Context.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value) } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -113,7 +113,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient }; request.AddMetaHeader(xHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); return request; } diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index f4d239a..e088ba4 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -49,7 +49,7 @@ internal class NetmapServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -63,7 +63,7 @@ internal class NetmapServiceProvider : ContextAccessor var request = new NetworkInfoRequest(); request.AddMetaHeader(null); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -79,7 +79,7 @@ internal class NetmapServiceProvider : ContextAccessor var request = new NetmapSnapshotRequest(); request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 1c8bf2f..6696aab 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -13,13 +13,13 @@ using FrostFS.Session; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ClientV2.Extensions; using FrostFS.SDK.ClientV2.Parameters; +using System.Buffers; namespace FrostFS.SDK.ClientV2; -internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment ctx) : ContextAccessor(ctx), ISessionProvider +internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) : ContextAccessor(env), ISessionProvider { - readonly ObjectTools tools = new(ctx); - readonly SessionProvider sessions = new (ctx); + readonly SessionProvider sessions = new (env); public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) { @@ -35,8 +35,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { Address = new Address { - ContainerId = args.ContainerId.ToGrpcMessage(), - ObjectId = args.ObjectId.ToGrpcMessage() + ContainerId = args.ContainerId.ToMessage(), + ObjectId = args.ObjectId.ToMessage() } } }; @@ -46,11 +46,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Head, - Context.Key); + Context.Key.ECDsaKey); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -69,8 +69,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { Address = new Address { - ContainerId = args.ContainerId.ToGrpcMessage(), - ObjectId = args.ObjectId.ToGrpcMessage() + ContainerId = args.ContainerId.ToMessage(), + ObjectId = args.ObjectId.ToMessage() } } }; @@ -80,11 +80,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Get, - Context.Key); + Context.Key.ECDsaKey); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); return await GetObject(request, ctx); } @@ -98,8 +98,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { Address = new Address { - ContainerId = args.ContainerId.ToGrpcMessage(), - ObjectId = args.ObjectId.ToGrpcMessage() + ContainerId = args.ContainerId.ToMessage(), + ObjectId = args.ObjectId.ToMessage() } } }; @@ -109,10 +109,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Delete, - Context.Key); + Context.Key.ECDsaKey); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -126,24 +126,24 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { Body = new SearchRequest.Types.Body { - ContainerId = args.ContainerId.ToGrpcMessage(), + ContainerId = args.ContainerId.ToMessage(), Filters = { }, Version = 1 // TODO: clarify this param } }; - request.Body.Filters.AddRange(args.Filters.Select(f => f.ToGrpcMessage())); + request.Body.Filters.AddRange(args.Filters.Select(f => f.ToMessage())); var sessionToken = await GetOrCreateSession(args, ctx); sessionToken.CreateObjectTokenContext( new Address { ContainerId = request.Body.ContainerId }, ObjectSessionContext.Types.Verb.Search, - Context.Key); + Context.Key.ECDsaKey); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var objectsIds = SearchObjects(request, ctx); @@ -153,24 +153,31 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } } - internal Task PutObjectAsync(PrmObjectPut args) + internal async Task PutObjectAsync(PrmObjectPut args) { if (args.Header == null) throw new ArgumentException("Value cannot be null", nameof(args.Header)); if (args.Payload == null) throw new ArgumentException("Value cannot be null", nameof(args.Payload)); - + if (args.ClientCut) - return PutClientCutObject(args); - else - return PutStreamObject(args); + return await PutClientCutObject(args); + else + { + if (args.Header.PayloadLength > 0) + args.FullLength = args.Header.PayloadLength; + else if (args.Payload.CanSeek) + args.FullLength = (ulong)args.Payload.Length; + + return (await PutStreamObject(args)).ObjectId; + } } internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) { var ctx = args.Context!; - var grpcObject = tools.CreateObject(args.FrostFsObject); + var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, env); var request = new PutSingleRequest { @@ -185,11 +192,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId}, ObjectSessionContext.Types.Verb.Put, - Context.Key); + Context.Key.ECDsaKey); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -201,156 +208,136 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C private async Task PutClientCutObject(PrmObjectPut args) { var ctx = args.Context!; + var tokenRaw = await GetOrCreateSession(args, ctx); var token = new ModelsV2.SessionToken(tokenRaw.Serialize()); + args.SessionToken = token; + var payloadStream = args.Payload!; var header = args.Header!; - ObjectId? objectId; - List sentObjectIds = []; - - FrostFsObject? currentObject; - - var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)); - - var objectSize = (int)networkSettings.MaxObjectSize; - var fullLength = header.PayloadLength; - - if (payloadStream.CanSeek) - { - objectSize = (int)Math.Min(objectSize, payloadStream.Length); - - if (fullLength == 0) - fullLength = (ulong)payloadStream.Length; - } - if (fullLength == 0) - throw new ArgumentException("Payload stream must be able to seek or PayloadLength must be specified"); - - var buffer = new byte[objectSize]; + if (payloadStream.CanSeek && fullLength == 0) + fullLength = (ulong)payloadStream.Length; - var largeObject = new LargeObject(header.ContainerId); + args.FullLength = fullLength; - var split = new Split(); - - while (true) + if (args.MaxObjectSizeCache == 0) { - var bytesCount = await payloadStream.ReadAsync(buffer, 0, objectSize); - - split.Previous = sentObjectIds.LastOrDefault(); - - largeObject.Header.PayloadLength += (ulong)bytesCount; - - currentObject = new FrostFsObject(header.ContainerId) - .SetPayload(bytesCount < objectSize ? buffer[..bytesCount] : buffer) - .SetSplit(split); - - if (largeObject.PayloadLength == fullLength) - break; - - objectId = await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token }); - - sentObjectIds.Add(objectId!); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)); + args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; } - if (sentObjectIds.Count != 0) + var restBytes = fullLength - args.CurrentStreamPosition; + var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache; + + //define collection capacity + var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; + var objectsCount = fullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; + + List sentObjectIds = new(objectsCount); + + Split? split = null; + + // keep attributes for the large object + var attributes = args.Header!.Attributes; + + // send all parts except the last one as separate Objects + while (restBytes > (ulong)args.MaxObjectSizeCache) { - largeObject.AddAttributes(args.Header!.Attributes); - - currentObject.SetParent(largeObject); + if (split == null) + { + split = new Split(); + args.Header!.Attributes = []; + } - var putSingleObjectParams = new PrmSingleObjectPut(currentObject, ctx) { SessionToken = token }; + split!.Previous = sentObjectIds.LastOrDefault(); + args.Header!.Split = split; - objectId = await PutSingleObjectAsync(putSingleObjectParams); + var result = await PutStreamObject(args); - sentObjectIds.Add(objectId); + sentObjectIds.Add(result.ObjectId); - var linkObject = new LinkObject(header.ContainerId, split.SplitId, largeObject) + restBytes -= (ulong)result.ObjectSize; + } + + // send the last part and create linkObject + if (sentObjectIds.Count > 0) + { + var largeObjectHeader = new ObjectHeader(header.ContainerId) { PayloadLength = fullLength }; + + largeObjectHeader.Attributes.AddRange(attributes); + + args.Header.Split!.ParentHeader = largeObjectHeader; + + var result = await PutStreamObject(args); + + sentObjectIds.Add(result.ObjectId); + + var linkObject = new LinkObject(header.ContainerId, split!.SplitId, largeObjectHeader) .AddChildren(sentObjectIds); - linkObject.Header.Attributes.Clear(); + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context}); - _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, ctx){ SessionToken = token }); - - return tools.CalculateObjectId(largeObject.Header); + return split.Parent!; } - currentObject - .SetSplit(null) - .AddAttributes(args.Header!.Attributes); - - return await PutSingleObjectAsync(new PrmSingleObjectPut(currentObject, ctx)); + // We are here if the payload is placed to one Object. It means no cut action, just simple PUT. + var singlePartResult = await PutStreamObject(args); + + return singlePartResult.ObjectId; } - private async Task PutStreamObject(PrmObjectPut args) + struct PutObjectResult(ObjectId objectId, int objectSize) + { + public ObjectId ObjectId = objectId; + public int ObjectSize = objectSize; + } + + private async Task PutStreamObject(PrmObjectPut args) { var ctx = args.Context!; var payload = args.Payload!; - var header = args.Header!; - var hdr = header.ToGrpcMessage(); - hdr.OwnerId = Context.Owner.ToGrpcMessage(); - hdr.Version = Context.Version.ToGrpcMessage(); + var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; - var oid = new ObjectID { Value = hdr.Sha256() }; + var restBytes = args.FullLength - args.CurrentStreamPosition; - var initRequest = new PutRequest + chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize); + + var chunkBuffer = ArrayPool.Shared.Rent(chunkSize); + var sentBytes = 0; + + // 0 means no limit from client, so server side cut is performed + var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; + + var stream = await GetUploadStream(args, ctx); + + while (objectLimitSize == 0 || sentBytes < objectLimitSize) { - Body = new PutRequest.Types.Body - { - Init = new PutRequest.Types.Body.Types.Init - { - Header = hdr - } - } - }; + // send chanks limited to default or user's settings + var bufferSize = objectLimitSize > 0 ? + (int)Math.Min(objectLimitSize - sentBytes, chunkSize) + : chunkSize; - var sessionToken = await GetOrCreateSession(args, ctx); - - sessionToken.CreateObjectTokenContext( - new Address { ContainerId = hdr.ContainerId, ObjectId = oid }, - ObjectSessionContext.Types.Verb.Put, - Context.Key - ); - - initRequest.AddMetaHeader(args.XHeaders, sessionToken); - - initRequest.Sign(Context.Key); - - using var stream = await PutObjectInit(initRequest, ctx); - - var bufferSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; - - if (payload.CanSeek) - { - bufferSize = (int)Math.Min(payload.Length, bufferSize); - } - else if (header.PayloadLength > 0) - { - bufferSize = (int)Math.Min((long)header.PayloadLength, bufferSize); - } - - var buffer = new byte[bufferSize]; - - while (true) - { - var bytesCount = await payload.ReadAsync(buffer, 0, bufferSize, ctx.CancellationToken); + var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken); if (bytesCount == 0) break; - var chunkRequest = new PutRequest(initRequest) + sentBytes += bytesCount; + + var chunkRequest = new PutRequest { Body = new PutRequest.Types.Body { - Chunk = ByteString.CopyFrom(buffer.AsSpan()[..bytesCount]), - }, - VerifyHeader = null + Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount) + } }; - chunkRequest.Sign(Context.Key); + chunkRequest.Sign(Context.Key.ECDsaKey); await stream.Write(chunkRequest); } @@ -358,7 +345,49 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var response = await stream.Close(); Verifier.CheckResponse(response); - return ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()); + return new PutObjectResult(ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); + } + + private async Task GetUploadStream(PrmObjectPut args, Context ctx) + { + var header = args.Header!; + + header.OwnerId = Context.Owner; + header.Version = Context.Version; + + var grpcHeader = header.ToMessage(); + + if (header.Split != null) + { + ObjectTools.SetSplitValues(grpcHeader, header.Split, env); + } + + var oid = new ObjectID { Value = grpcHeader.Sha256() }; + + var initRequest = new PutRequest + { + Body = new PutRequest.Types.Body + { + Init = new PutRequest.Types.Body.Types.Init + { + Header = grpcHeader + } + } + }; + + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateObjectTokenContext( + new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid }, + ObjectSessionContext.Types.Verb.Put, + Context.Key.ECDsaKey + ); + + initRequest.AddMetaHeader(args.XHeaders, sessionToken); + + initRequest.Sign(Context.Key.ECDsaKey); + + return await PutObjectInit(initRequest, ctx); } private async Task GetObject(GetRequest request, Context ctx) diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 61ea93f..0bb5499 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -21,13 +21,13 @@ internal class SessionServiceProvider : ContextAccessor { Body = new CreateRequest.Types.Body { - OwnerId = Context.Owner.ToGrpcMessage(), + OwnerId = Context.Owner.ToMessage(), Expiration = args.Expiration } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key); + request.Sign(Context.Key.ECDsaKey); return await CreateSession(request, args.Context!); } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index dae8ff2..c1f4d84 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,7 +1,9 @@ using FrostFS.SDK.ModelsV2; +using Google.Protobuf; using Grpc.Net.Client; using System; using System.Security.Cryptography; +using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.ClientV2; @@ -9,11 +11,12 @@ public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChan { internal OwnerId Owner { get; } = owner; internal GrpcChannel Channel { get; private set; } = channel; - internal ECDsa Key { get; } = key; internal ModelsV2.Version Version { get; } = version; internal NetworkSettings? NetworkSettings { get; set; } - internal Client Client { get; set; } = client; + internal Client Client { get; } = client; + + internal ClientKey Key { get; } = new ClientKey(key); public void Dispose() { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index f8d2c33..aad073b 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -7,54 +7,36 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; +using static FrostFS.Object.Header.Types; namespace FrostFS.SDK.ClientV2; -internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) +internal class ObjectTools { - internal ObjectId CalculateObjectId(ObjectHeader header) + internal static ObjectId CalculateObjectId(ObjectHeader header, ClientEnvironment env) { - var grpcHeader = CreateHeader(header, []); + var grpcHeader = CreateHeader(header, [], env); + + if (header.Split != null) + SetSplitValues(grpcHeader, header.Split, env); return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal Object.Object CreateObject(FrostFsObject @object) + internal static Object.Object CreateObject(FrostFsObject @object, ClientEnvironment env) { - var grpcHeader = @object.Header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); + @object.Header.OwnerId = env.Owner; + @object.Header.Version = env.Version; + + var grpcHeader = @object.Header.ToMessage(); + grpcHeader.PayloadLength = (ulong)@object.Payload.Length; grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); - + var split = @object.Header.Split; if (split != null) { - grpcHeader.Split = new Header.Types.Split - { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null - }; - - if (split.Children != null && split.Children.Count != 0) - grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToGrpcMessage())); - - if (split.ParentHeader is not null) - { - var grpcParentHeader = CreateHeader(split.ParentHeader, []); - - grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; - grpcHeader.Split.ParentHeader = grpcParentHeader; - grpcHeader.Split.ParentSignature = new Refs.Signature - { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), - }; - - split.Parent = grpcHeader.Split.Parent.ToModel(); - } - - grpcHeader.Split.Previous = split.Previous?.ToGrpcMessage(); + SetSplitValues(grpcHeader, split, env); } var obj = new Object.Object @@ -66,21 +48,49 @@ internal class ObjectTools(ClientEnvironment ctx) : ContextAccessor (ctx) obj.Signature = new Refs.Signature { - Key = ByteString.CopyFrom(Context.Key.PublicKey()), - Sign = ByteString.CopyFrom(Context.Key.SignData(obj.ObjectId.ToByteArray())), + Key = env.Key.PublicKeyProto, + Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(obj.ObjectId.ToByteArray())), }; return obj; } - internal Header CreateHeader(ObjectHeader header, byte[]? payload) + internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, ClientEnvironment env) { - var grpcHeader = header.ToGrpcMessage(); - - grpcHeader.OwnerId = Context.Owner.ToGrpcMessage(); - grpcHeader.Version = Context.Version.ToGrpcMessage(); + grpcHeader.Split = new Header.Types.Split + { + SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + }; - if (payload != null) + if (split.Children != null && split.Children.Count != 0) + grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToMessage())); + + if (split.ParentHeader is not null) + { + var grpcParentHeader = CreateHeader(split.ParentHeader, [], env); + + grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.ParentHeader = grpcParentHeader; + grpcHeader.Split.ParentSignature = new Refs.Signature + { + Key = env.Key.PublicKeyProto, + Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray())), + }; + + split.Parent = grpcHeader.Split.Parent.ToModel(); + } + + grpcHeader.Split.Previous = split.Previous?.ToMessage(); + } + + internal static Header CreateHeader(ObjectHeader header, byte[]? payload, ClientEnvironment env) + { + var grpcHeader = header.ToMessage(); + + grpcHeader.OwnerId = env.Owner.ToMessage(); + grpcHeader.Version = env.Version.ToMessage(); + + if (payload != null) // && payload.Length > 0 grpcHeader.PayloadHash = Sha256Checksum(payload); return grpcHeader; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index 4e1de51..acced67 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -17,7 +17,7 @@ public static class RequestConstructor if (request.MetaHeader is not null) return; - request.MetaHeader = MetaHeader.Default().ToGrpcMessage(); + request.MetaHeader = MetaHeader.Default().ToMessage(); if (sessionToken != null) request.MetaHeader.SessionToken = sessionToken; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index d9fa633..f5057eb 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -69,7 +69,7 @@ public static class RequestSigner var hash = new byte[65]; hash[0] = 0x04; - key.SignHash(SHA512.Create().ComputeHash(data)).CopyTo(hash, 1); + key.SignHash(data.Sha512()).CopyTo(hash, 1); return hash; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 2b36a64..bd8ddec 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -60,7 +60,7 @@ public static class Verifier public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig) { - return key.VerifyHash(SHA512.Create().ComputeHash(data), sig[1..]); + return key.VerifyHash(data.Sha512(), sig[1..]); } public static bool VerifyMessagePart(this Signature sig, IMessage data) @@ -101,7 +101,8 @@ public static class Verifier throw new FormatException($"invalid response, type={resp.GetType()}"); var status = resp.MetaHeader.Status.ToModel(); - if (!status.IsSuccess) + + if (status != null && !status.IsSuccess) throw new ResponseException(status); } diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index f33794a..95b07f0 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -51,13 +51,13 @@ public static class Base58 int digit = Alphabet.IndexOf(input[i]); if (digit < 0) throw new FormatException($"Invalid Base58 character '{input[i]}' at position {i}"); - + bi = bi * Alphabet.Length + digit; } int leadingZeroCount = input.TakeWhile(c => c == Alphabet[0]).Count(); var leadingZeros = new byte[leadingZeroCount]; - + if (bi.IsZero) return leadingZeros; @@ -65,9 +65,9 @@ public static class Base58 var firstNonZeroIndex = 0; - while(bytesBigEndian.ElementAt(firstNonZeroIndex) == 0x0) + while (bytesBigEndian.ElementAt(firstNonZeroIndex) == 0x0) firstNonZeroIndex++; - + var bytesWithoutLeadingZeros = bytesBigEndian.Skip(firstNonZeroIndex).ToArray(); return ArrayHelper.Concat(leadingZeros, bytesWithoutLeadingZeros); diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index 534fb7d..32c8259 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,11 +1,18 @@ using Google.Protobuf; using Org.BouncyCastle.Crypto.Digests; using System.Security.Cryptography; +using System.Threading; namespace FrostFS.SDK.Cryptography; public static class Extentions { + private static readonly SHA256 _sha256 = SHA256.Create(); + private static SpinLock _spinlockSha256 = new(); + + private static readonly SHA512 _sha512 = SHA512.Create(); + private static SpinLock _spinlockSha512 = new(); + internal static byte[] RIPEMD160(this byte[] value) { var hash = new byte[20]; @@ -15,15 +22,41 @@ public static class Extentions digest.DoFinal(hash, 0); return hash; } - - public static byte[] Sha256(this byte[] value) - { - var sha256 = SHA256.Create(); - return sha256.ComputeHash(value); - } public static ByteString Sha256(this IMessage data) { return ByteString.CopyFrom(data.ToByteArray().Sha256()); } + + public static byte[] Sha256(this byte[] value) + { + bool lockTaken = false; + try + { + _spinlockSha256.Enter(ref lockTaken); + + return _sha256.ComputeHash(value); + } + finally + { + if (lockTaken) + _spinlockSha256.Exit(false); + } + } + + public static byte[] Sha512(this byte[] value) + { + bool lockTaken = false; + try + { + _spinlockSha512.Enter(ref lockTaken); + + return _sha512.ComputeHash(value); + } + finally + { + if (lockTaken) + _spinlockSha512.Exit(false); + } + } } diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index de96791..bbffab8 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -133,8 +133,9 @@ public static class KeyExtension public static ECDsa LoadPrivateKey(this byte[] privateKey) { var secp256R1 = SecNamedCurves.GetByName("secp256r1"); - var publicKey = - secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)).GetEncoded(false)[1..]; + var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)) + .GetEncoded(false)[1..]; + var key = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, diff --git a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs index 1a846ab..5cb35a1 100644 --- a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs +++ b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs @@ -112,7 +112,7 @@ internal class Murmur3_128 : HashAlgorithm h2 = seed; } - private ulong Fimix64(ulong k) + private static ulong Fimix64(ulong k) { k ^= k >> 33; k *= 0xff51afd7ed558ccd; diff --git a/src/FrostFS.SDK.Cryptography/Range.cs b/src/FrostFS.SDK.Cryptography/Range.cs index 7313a64..92aa82f 100644 --- a/src/FrostFS.SDK.Cryptography/Range.cs +++ b/src/FrostFS.SDK.Cryptography/Range.cs @@ -77,14 +77,7 @@ namespace System { get { - if (_value < 0) - { - return ~_value; - } - else - { - return _value; - } + return _value < 0 ? ~_value : _value; } } @@ -105,12 +98,9 @@ namespace System var offset = _value; if (IsFromEnd) { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - offset += length + 1; } + return offset; } diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj index be180b0..1b4ad74 100644 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj @@ -16,6 +16,7 @@ + diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs index 6b5526a..0b7f3bd 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs @@ -1,3 +1,4 @@ +using FrostFS.SDK.Cryptography; using System; using System.Security.Cryptography; @@ -8,15 +9,9 @@ public class CheckSum // type is always Sha256 public byte[]? Hash { get; set; } - public static byte[] GetHash(byte[] content) - { - var sha256 = SHA256.Create(); - return sha256.ComputeHash(content); - } - public static CheckSum CreateCheckSum(byte[] content) { - return new CheckSum { Hash = GetHash(content) }; + return new CheckSum { Hash = content.Sha256() }; } public override string ToString() diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index 6ca2c22..971d41d 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -56,19 +56,17 @@ public class FrostFsObject /// Applied only for the last Object in chain in case of manual multipart uploading /// /// Parent for multipart object - public void SetParent(LargeObject largeObject) + public void SetParent(ObjectHeader largeObjectHeader) { if (Header?.Split == null) throw new Exception("The object is not initialized properly"); - Header.Split.ParentHeader = largeObject.Header; + Header.Split.ParentHeader = largeObjectHeader; } } public class LargeObject(ContainerId container) : FrostFsObject(container) { - private readonly SHA256 payloadHash = SHA256.Create(); - public ulong PayloadLength { get { return Header!.PayloadLength; } @@ -77,11 +75,11 @@ public class LargeObject(ContainerId container) : FrostFsObject(container) public class LinkObject : FrostFsObject { - public LinkObject(ContainerId containerId, SplitId splitId, LargeObject largeObject) : base (containerId) + public LinkObject(ContainerId containerId, SplitId splitId, ObjectHeader largeObjectHeader) : base (containerId) { Header!.Split = new Split(splitId) { - ParentHeader = largeObject.Header + ParentHeader = largeObjectHeader }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs index d4622df..3fb55df 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; + namespace FrostFS.SDK.ModelsV2; public class Split(SplitId splitId) @@ -19,4 +20,6 @@ public class Split(SplitId splitId) public ObjectHeader? ParentHeader { get; set; } public List Children { get; } = []; + + public Refs.Signature ParentSignatureGrpc { get; set; } } diff --git a/src/FrostFS.SDK.ModelsV2/Response/Status.cs b/src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs similarity index 83% rename from src/FrostFS.SDK.ModelsV2/Response/Status.cs rename to src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs index d52d189..f58e1bb 100644 --- a/src/FrostFS.SDK.ModelsV2/Response/Status.cs +++ b/src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs @@ -2,7 +2,7 @@ using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; -public class Status(StatusCode code, string? message = null) +public class ResponseStatus(StatusCode code, string? message = null) { public StatusCode Code { get; set; } = code; public string Message { get; set; } = message ?? string.Empty; diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 8fbdc2a..74f80e3 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -110,6 +110,6 @@ public class ContainerTest : ContainerTestsBase var request = Mocker.Requests.First(); - Assert.Equal(cid.ToGrpcMessage(), request.Request.Body.ContainerId); + Assert.Equal(cid.ToMessage(), request.Request.Body.ContainerId); } } diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 45b17bc..3603a92 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -19,14 +19,14 @@ public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : Serv { var header = new Header { - ContainerId = objectHeader.ContainerId.ToGrpcMessage(), + ContainerId = objectHeader.ContainerId.ToMessage(), PayloadLength = objectHeader.PayloadLength, - Version = objectHeader.Version!.ToGrpcMessage(), - OwnerId = objectHeader.OwnerId!.ToGrpcMessage() + Version = objectHeader.Version!.ToMessage(), + OwnerId = objectHeader.OwnerId!.ToMessage() }; foreach (var attr in objectHeader.Attributes) - header.Attributes.Add(attr.ToGrpcMessage()); + header.Attributes.Add(attr.ToMessage()); var response = new GetResponse { diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 530294a..9de903c 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -27,7 +27,7 @@ public abstract class ServiceBase(string key) public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); - public Metadata ResponseMetaData => []; + public static Metadata ResponseMetaData => []; protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) { @@ -58,7 +58,7 @@ public abstract class ServiceBase(string key) public ResponseMetaHeader ResponseMetaHeader => new() { - Version = Version.ToGrpcMessage(), + Version = Version.ToMessage(), Epoch = 100, Ttl = 1 }; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index aff566d..34f6173 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -19,7 +19,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) { var mock = new Mock(); - var grpcVersion = Version.ToGrpcMessage(); + var grpcVersion = Version.ToMessage(); PutResponse putResponse = new() { @@ -32,7 +32,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) }, MetaHeader = new ResponseMetaHeader { - Version = Version is null ? DefaultVersion.ToGrpcMessage() : Version.ToGrpcMessage(), + Version = (Version is null ? DefaultVersion : Version).ToMessage(), Epoch = 100, Ttl = 1 } @@ -69,7 +69,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) Version = grpcVersion, Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), BasicAcl = (uint)Acl, - PlacementPolicy = PlacementPolicy.ToGrpcMessage() + PlacementPolicy = PlacementPolicy.ToMessage() } }, MetaHeader = ResponseMetaHeader diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index db4df1e..099398d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -38,12 +38,12 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) HeadResponse ??= new Header { CreationEpoch = 99, - ContainerId = ObjectHeader.ContainerId.ToGrpcMessage(), + ContainerId = ObjectHeader.ContainerId.ToMessage(), ObjectType = ObjectType.Regular, - OwnerId = ObjectHeader.OwnerId!.ToGrpcMessage(), + OwnerId = ObjectHeader.OwnerId!.ToMessage(), PayloadLength = 1, PayloadHash = new Refs.Checksum { Type = Refs.ChecksumType.Sha256, Sum = ByteString.CopyFrom(SHA256.HashData([0xff])) }, - Version = ObjectHeader.Version!.ToGrpcMessage() + Version = ObjectHeader.Version!.ToMessage() }; HeadResponse headResponse = new() @@ -89,7 +89,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) } - if (ResultObjectId != null) + if (ResultObjectIds != null) { PutResponse putResponse = new() { @@ -97,7 +97,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) { ObjectId = new Refs.ObjectID { - Value = ByteString.CopyFrom(ResultObjectId) + Value = ByteString.CopyFrom(ResultObjectIds.ElementAt(0)) } }, MetaHeader = ResponseMetaHeader, @@ -156,8 +156,8 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) { Tombstone = new Refs.Address { - ContainerId = ObjectHeader!.ContainerId.ToGrpcMessage(), - ObjectId = ObjectId.ToGrpcMessage() + ContainerId = ObjectHeader!.ContainerId.ToMessage(), + ObjectId = ObjectId.ToMessage() } }, MetaHeader = ResponseMetaHeader @@ -195,7 +195,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) public Header? HeadResponse { get; set; } - public byte[]? ResultObjectId { get; set; } + public List? ResultObjectIds { get; set; } public ClientStreamWriter? ClientStreamWriter { get; private set; } = new (); diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 05573ff..6fb3f0a 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -7,6 +7,7 @@ using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.SDK.ProtosV2.Interfaces; using Google.Protobuf; using Microsoft.Extensions.Options; using System.Security.Cryptography; @@ -82,7 +83,7 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(result); Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value); - Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), result.Header.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); Assert.Single(result.Header.Attributes); Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); @@ -92,7 +93,7 @@ public class ObjectTest : ObjectTestsBase [Fact] public async void PutObjectTest() { - Mocker.ResultObjectId = SHA256.HashData([]); + Mocker.ResultObjectIds = new([SHA256.HashData([])]); Random rnd = new(); var bytes = new byte[1024]; @@ -113,7 +114,7 @@ public class ObjectTest : ObjectTestsBase var body2 = sentMessages.ElementAt(1).GetBody() as Object.PutRequest.Types.Body; Assert.NotNull(result); - Assert.Equal(Mocker.ResultObjectId, result.ToHash()); + Assert.Equal(Mocker.ResultObjectIds.First(), result.ToHash()); Assert.True(Mocker.ClientStreamWriter.CompletedTask); @@ -137,57 +138,85 @@ public class ObjectTest : ObjectTestsBase { Header = Mocker.ObjectHeader, Payload = new MemoryStream(bytes), + BufferMaxSize = 1024, ClientCut = true }; + Random rnd = new(); + + List objIds = new([new byte[32], new byte[32], new byte[32]]); + rnd.NextBytes(objIds.ElementAt(0)); + rnd.NextBytes(objIds.ElementAt(1)); + rnd.NextBytes(objIds.ElementAt(2)); + + Mocker.ResultObjectIds = objIds; + var result = await GetClient().PutObjectAsync(param); - var sentMessages = Mocker.PutSingleRequests.ToArray(); + var singleObjects = Mocker.PutSingleRequests.ToArray(); - Assert.Equal(4, sentMessages.Length); + var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray(); - var object_0 = sentMessages[0].Body.Object; - var object_1 = sentMessages[1].Body.Object; - var object_2 = sentMessages[2].Body.Object; - var object_3 = sentMessages[3].Body.Object; + Assert.Single(singleObjects); - Assert.NotNull(object_0.Header.Split.SplitId); - Assert.Null(object_0.Header.Split.Previous); - Assert.Equal(blockSize, (int)object_0.Header.PayloadLength); - Assert.Equal(bytes[..blockSize], object_0.Payload); - Assert.True(object_0.Header.Attributes.Count == 0); + Assert.Equal(11, streamObjects.Length); - Assert.Equal(object_0.Header.Split.SplitId, object_1.Header.Split.SplitId); - Assert.Equal(object_0.ObjectId, object_1.Header.Split.Previous); - Assert.Equal(blockSize, (int)object_1.Header.PayloadLength); - Assert.Equal(bytes[blockSize..(blockSize * 2)], object_1.Payload); - Assert.True(object_1.Header.Attributes.Count == 0); + var bodies = streamObjects.Select(o => ((Object.PutRequest)o).Body).ToArray(); + + Assert.Equal(3, bodies.Count(b => b.Init != null)); + Assert.Equal(5, bodies.Count(b => b.Chunk.Length == 1024)); + + Assert.Equal(596, ((Object.PutRequest)streamObjects[10]).Body.Chunk.Length); + + var linkObject = singleObjects[0].Body.Object; + var header1 = bodies[0].Init.Header; + var header2 = bodies[4].Init.Header; + var header3 = bodies[8].Init.Header; + + var payload1 = bodies[1].Chunk + .Concat(bodies[2].Chunk) + .Concat(bodies[3].Chunk) + .ToArray(); + + var payload2 = bodies[5].Chunk + .Concat(bodies[6].Chunk) + .Concat(bodies[7].Chunk) + .ToArray(); + + var payload3 = bodies[9].Chunk + .Concat(bodies[10].Chunk) + .ToArray(); + + Assert.NotNull(header1.Split.SplitId); + Assert.Null(header1.Split.Previous); + Assert.Equal(bytes[..blockSize], payload1); + Assert.True(header1.Attributes.Count == 0); + + Assert.Equal(header1.Split.SplitId, header2.Split.SplitId); + Assert.Equal(objIds.ElementAt(0), header2.Split.Previous.Value); + Assert.Equal(bytes[blockSize..(blockSize * 2)], payload2); + Assert.True(header2.Attributes.Count == 0); // last part - Assert.NotNull(object_2.Header.Split.Parent); - Assert.NotNull(object_2.Header.Split.ParentHeader); - Assert.NotNull(object_2.Header.Split.ParentSignature); - Assert.Equal(object_1.Header.Split.SplitId, object_2.Header.Split.SplitId); - Assert.Equal(object_1.ObjectId, object_2.Header.Split.Previous); - Assert.Equal(fileLength%blockSize, (int)object_2.Header.PayloadLength); - Assert.Equal(bytes[((fileLength/blockSize) * blockSize)..fileLength], object_2.Payload); - Assert.True(object_2.Header.Attributes.Count == 0); + Assert.NotNull(header3.Split.Parent); + Assert.NotNull(header3.Split.ParentHeader); + Assert.NotNull(header3.Split.ParentSignature); + Assert.Equal(header2.Split.SplitId, header3.Split.SplitId); + Assert.Equal(bytes[((fileLength / blockSize) * blockSize)..fileLength], payload3); + Assert.True(header3.Attributes.Count == 0); - // link object - Assert.Equal(object_2.Header.Split.Parent, object_3.Header.Split.Parent); - Assert.Equal(object_2.Header.Split.ParentHeader, object_3.Header.Split.ParentHeader); - Assert.Equal(object_2.Header.Split.SplitId, object_3.Header.Split.SplitId); - Assert.Equal(0, (int)object_3.Header.PayloadLength); - Assert.Contains(object_0.ObjectId, object_3.Header.Split.Children); - Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); - Assert.Contains(object_2.ObjectId, object_3.Header.Split.Children); - Assert.True(object_2.Header.Attributes.Count == 0); + //link object + Assert.Equal(header3.Split.Parent, linkObject.Header.Split.Parent); + Assert.Equal(header3.Split.ParentHeader, linkObject.Header.Split.ParentHeader); + Assert.Equal(header3.Split.SplitId, linkObject.Header.Split.SplitId); + Assert.Equal(0, (int)linkObject.Header.PayloadLength); + Assert.True(header3.Attributes.Count == 0); - Assert.Single(object_3.Header.Split.ParentHeader.Attributes); - Assert.Equal("k", object_3.Header.Split.ParentHeader.Attributes[0].Key); - Assert.Equal("v", object_3.Header.Split.ParentHeader.Attributes[0].Value); + Assert.Single(linkObject.Header.Split.ParentHeader.Attributes); + Assert.Equal("k", linkObject.Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", linkObject.Header.Split.ParentHeader.Attributes[0].Value); - var modelObjId = ObjectId.FromHash(object_3.Header.Split.Parent.Value.ToByteArray()); + var modelObjId = ObjectId.FromHash(linkObject.Header.Split.Parent.Value.ToByteArray()); Assert.Equal(result.Value, modelObjId.ToString()); } @@ -201,8 +230,8 @@ public class ObjectTest : ObjectTestsBase var request = Mocker.DeleteRequests.FirstOrDefault(); Assert.NotNull(request); - Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); - Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + Assert.Equal(ContainerId.ToMessage().Value, request.Body.Address.ContainerId.Value); + Assert.Equal(Mocker.ObjectId.ToMessage().Value, request.Body.Address.ObjectId.Value); } [Fact] @@ -214,13 +243,13 @@ public class ObjectTest : ObjectTestsBase var request = Mocker.HeadRequests.FirstOrDefault(); Assert.NotNull(request); - Assert.Equal(ContainerId.ToGrpcMessage(), request.Body.Address.ContainerId); - Assert.Equal(Mocker.ObjectId.ToGrpcMessage(), request.Body.Address.ObjectId); + Assert.Equal(ContainerId.ToMessage(), request.Body.Address.ContainerId); + Assert.Equal(Mocker.ObjectId.ToMessage(), request.Body.Address.ObjectId); Assert.NotNull(response); Assert.Equal(ContainerId.Value, response.ContainerId.Value); - Assert.Equal(Mocker.ObjectHeader!.OwnerId!.ToGrpcMessage().ToString(), response.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, response.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index f827677..8b89531 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -72,13 +72,14 @@ public class SmokeTests [Fact] public async void GetSessionTest() { + var ecdsaKey = this.key.LoadWif(); + using var client = Client.GetInstance(GetOptions(this.key, this.url)); var token = await client.CreateSessionAsync(new PrmSessionCreate(100)); var session = new Session.SessionToken().Deserialize(token.Token); - - var ecdsaKey = this.key.LoadWif(); + var owner = OwnerId.FromKey(ecdsaKey); var ownerHash = Base58.Decode(owner.Value); @@ -217,9 +218,10 @@ public class SmokeTests await foreach (var objId in client.SearchObjectsAsync(searchParam)) { resultObjectsCount++; + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId)); } - Assert.True(1 == resultObjectsCount, $"Filter for {filter.Key} doesn't work"); + Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); } [Theory] @@ -403,14 +405,14 @@ public class SmokeTests [InlineData(2 * 64 * 1024 * 1024 + 256)] [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) - { + { 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 + WaitParams = lightWait }; var containerId = await client.CreateContainerAsync(createContainerParam); @@ -449,8 +451,8 @@ public class SmokeTests var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); + Assert.Equal("fileName", objHeader.Attributes[0].Key); + Assert.Equal("test", objHeader.Attributes[0].Value); } Assert.True(hasObject); @@ -542,7 +544,7 @@ public class MetricsInterceptor() : Interceptor watch.Stop(); - // Do somethins with call info + // Do something with call info // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; return response; From 608383458271d4bbd89c9ed887f774cffa90833b Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 1 Aug 2024 18:01:58 +0300 Subject: [PATCH 15/65] [#20] Client: Fix typo Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index ac9634d..65199ca 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -15,14 +15,14 @@ public static class ContainerIdMapper public static ContainerID ToMessage(this ContainerId model) { - if (!Cache.Owners.TryGetValue(model, out ContainerID? message)) + if (!Cache.Containers.TryGetValue(model, out ContainerID? message)) { message = new ContainerID { Value = ByteString.CopyFrom(Base58.Decode(model.Value)) }; - Cache.Owners.Set(model, message, _oneHourExpiration); + Cache.Containers.Set(model, message, _oneHourExpiration); } return message!; From 18126ea763e3e1a7745e2fb429bc15be243df05e Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 5 Aug 2024 11:21:05 +0300 Subject: [PATCH 16/65] [#20] Optimize memory usage Provide custom buffer and use ArrayPool Signed-off-by: Pavel Gross --- .../Parameters/PrmObjectPut.cs | 10 ++- .../Services/ObjectServiceProvider.cs | 88 ++++++++++++------- .../Tools/ClientEnvironment.cs | 13 +++ 3 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index cd859d7..fea3bd5 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -1,6 +1,7 @@ -using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; +using System.Collections.Specialized; using System.IO; -using FrostFS.SDK.ModelsV2; + namespace FrostFS.SDK.ClientV2.Parameters; public sealed class PrmObjectPut : IContext, ISessionToken @@ -31,6 +32,11 @@ public sealed class PrmObjectPut : IContext, ISessionToken /// Size of the buffer public int BufferMaxSize { get; set; } + /// + /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. + /// + public byte[]? CustomBuffer { get; set; } + /// /// FrostFS request X-Headers /// diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 6696aab..26ba3e8 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -306,46 +306,68 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var restBytes = args.FullLength - args.CurrentStreamPosition; chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize); + + bool isRentBuffer = false; + byte[]? chunkBuffer = null; - var chunkBuffer = ArrayPool.Shared.Rent(chunkSize); - var sentBytes = 0; - - // 0 means no limit from client, so server side cut is performed - var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; - - var stream = await GetUploadStream(args, ctx); - - while (objectLimitSize == 0 || sentBytes < objectLimitSize) + try { - // send chanks limited to default or user's settings - var bufferSize = objectLimitSize > 0 ? - (int)Math.Min(objectLimitSize - sentBytes, chunkSize) - : chunkSize; - - var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken); - - if (bytesCount == 0) - break; - - sentBytes += bytesCount; - - var chunkRequest = new PutRequest + if (args.CustomBuffer != null) { - Body = new PutRequest.Types.Body + chunkBuffer = args.CustomBuffer; + } + else + { + chunkBuffer = env.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + isRentBuffer = true; + } + + var sentBytes = 0; + + // 0 means no limit from client, so server side cut is performed + var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; + + var stream = await GetUploadStream(args, ctx); + + while (objectLimitSize == 0 || sentBytes < objectLimitSize) + { + // send chanks limited to default or user's settings + var bufferSize = objectLimitSize > 0 ? + (int)Math.Min(objectLimitSize - sentBytes, chunkSize) + : chunkSize; + + var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken); + + if (bytesCount == 0) + break; + + sentBytes += bytesCount; + + var chunkRequest = new PutRequest { - Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount) - } - }; + Body = new PutRequest.Types.Body + { + Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount) + } + }; + + chunkRequest.Sign(Context.Key.ECDsaKey); - chunkRequest.Sign(Context.Key.ECDsaKey); + await stream.Write(chunkRequest); + } - await stream.Write(chunkRequest); + var response = await stream.Close(); + Verifier.CheckResponse(response); + + return new PutObjectResult(ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); + } + finally + { + if (isRentBuffer && chunkBuffer != null) + { + ArrayPool.Shared.Return(chunkBuffer); + } } - - var response = await stream.Close(); - Verifier.CheckResponse(response); - - return new PutObjectResult(ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); } private async Task GetUploadStream(PrmObjectPut args, Context ctx) diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index c1f4d84..4f4fa1b 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -4,11 +4,14 @@ using Grpc.Net.Client; using System; using System.Security.Cryptography; using FrostFS.SDK.Cryptography; +using System.Buffers; namespace FrostFS.SDK.ClientV2; public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable { + private ArrayPool _arrayPool; + internal OwnerId Owner { get; } = owner; internal GrpcChannel Channel { get; private set; } = channel; internal ModelsV2.Version Version { get; } = version; @@ -18,6 +21,16 @@ public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChan internal ClientKey Key { get; } = new ClientKey(key); + /// + /// Custom pool is used for predefined sizes of buffers like grpc chunk + /// + internal ArrayPool GetArrayPool(int size) + { + _arrayPool ??= ArrayPool.Create(size, 256); + + return _arrayPool; + } + public void Dispose() { Dispose(true); From 2a28806ace76a79f42bd5d1805a2fa1da5ca20d4 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 12 Aug 2024 10:46:59 +0300 Subject: [PATCH 17/65] [#21] Client: Allows multinenant client Using one client for several owners Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 73 ++++++++++-- .../Interfaces/IFrostFSClient.cs | 2 +- .../Parameters}/Context.cs | 28 ++++- .../Parameters/Credentials.cs | 11 ++ .../Parameters/PrmBase.cs | 14 +++ .../Parameters/PrmContainerCreate.cs | 12 +- .../Parameters/PrmContainerDelete.cs | 13 +-- .../Parameters/PrmContainerGet.cs | 13 +-- .../Parameters/PrmContainerGetAll.cs | 9 +- .../Parameters/PrmNetmapSnapshot.cs | 11 +- .../Parameters/PrmNetworkSettings.cs | 15 +-- .../Parameters/PrmNodeInfo.cs | 15 +-- .../Parameters/PrmObjectDelete.cs | 11 +- .../Parameters/PrmObjectGet.cs | 14 +-- .../Parameters/PrmObjectHeadGet.cs | 16 +-- .../Parameters/PrmObjectPut.cs | 14 +-- .../Parameters/PrmObjectSearch.cs | 17 +-- .../Parameters/PrmSessionCreate.cs | 14 +-- .../Parameters/PrmSingleObjectPut.cs | 14 +-- .../Services/ContainerServiceProvider.cs | 24 ++-- .../Services/NetmapServiceProvider.cs | 6 +- .../Services/ObjectServiceProvider.cs | 40 +++---- .../Services/SessionServiceProvider.cs | 5 +- .../Services/Shared/SessionProvider.cs | 2 +- .../Tools/ClientEnvironment.cs | 11 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 33 +++--- .../Client/ClientSettings.cs | 64 ++++++++--- src/FrostFS.SDK.Tests/ContainerTest.cs | 4 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 19 ++-- src/FrostFS.SDK.Tests/SmokeTests.cs | 106 ++++++++++++------ 30 files changed, 349 insertions(+), 281 deletions(-) rename src/{FrostFS.SDK.ModelsV2/Client => FrostFS.SDK.ClientV2/Parameters}/Context.cs (54%) create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index faf1802..0142c03 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -35,6 +35,11 @@ public class Client : IFrostFSClient return new Client(clientOptions, channelOptions); } + public static IFrostFSClient GetSingleOwnerInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) + { + return new Client(clientOptions, channelOptions); + } + /// /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. /// @@ -46,7 +51,7 @@ public class Client : IFrostFSClient /// Object.ObjectService.ObjectServiceClient implementation /// public static IFrostFSClient GetTestInstance( - IOptions clientOptions, + IOptions clientOptions, GrpcChannelOptions? channelOptions, NetmapService.NetmapServiceClient netmapService, SessionService.SessionServiceClient sessionService, @@ -57,18 +62,18 @@ public class Client : IFrostFSClient } private Client( - IOptions settings, - GrpcChannelOptions? channelOptions, - ContainerService.ContainerServiceClient containerService, - NetmapService.NetmapServiceClient netmapService, - SessionService.SessionServiceClient sessionService, - ObjectService.ObjectServiceClient objectService) + IOptions settings, + GrpcChannelOptions? channelOptions, + ContainerService.ContainerServiceClient containerService, + NetmapService.NetmapServiceClient netmapService, + SessionService.SessionServiceClient sessionService, + ObjectService.ObjectServiceClient objectService) { var ecdsaKey = settings.Value.Key.LoadWif(); OwnerId.FromKey(ecdsaKey); ClientCtx = new ClientEnvironment( - this, + client: this, key: ecdsaKey, owner: OwnerId.FromKey(ecdsaKey), channel: InitGrpcChannel(settings.Value.Host, channelOptions), @@ -86,6 +91,25 @@ public class Client : IFrostFSClient clientSettings.Validate(); + var channel = InitGrpcChannel(clientSettings.Host, channelOptions); + + ClientCtx = new ClientEnvironment( + this, + key: null, + owner: null, + channel: channel, + version: new Version(2, 13)); + + // TODO: define timeout logic + // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); + } + + private Client(IOptions options, GrpcChannelOptions? channelOptions) + { + var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); + + clientSettings.Validate(); + var ecdsaKey = clientSettings.Key.LoadWif(); var channel = InitGrpcChannel(clientSettings.Host, channelOptions); @@ -221,22 +245,22 @@ public class Client : IFrostFSClient #endregion #region ToolsImplementation - public ObjectId CalculateObjectId(ObjectHeader header) + public ObjectId CalculateObjectId(ObjectHeader header, Context ctx) { if (header == null) throw new ArgumentNullException(nameof(header)); - return ObjectTools.CalculateObjectId(header, ClientCtx); + return ObjectTools.CalculateObjectId(header, ctx); } #endregion private async void CheckFrostFsVersionSupport(Context? ctx = default) { - var args = new PrmNodeInfo(ctx); + var args = new PrmNodeInfo { Context = ctx }; var service = GetNetmapService(args); var localNodeInfo = await service.GetLocalNodeInfoAsync(args); - if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) + if (!localNodeInfo.Version.IsSupported(args.Context!.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; throw new ApplicationException(msg); @@ -250,6 +274,31 @@ public class Client : IFrostFSClient ctx.Context ??= new Context(); + if (ctx.Context.Key == null) + { + if (ClientCtx.Key == null) + { + throw new Exception("Key is not initialized."); + } + + ctx.Context.Key = ClientCtx.Key.ECDsaKey; + } + + if (ctx.Context.OwnerId == null) + { + ctx.Context.OwnerId = ClientCtx.Owner ?? OwnerId.FromKey(ctx.Context.Key); + } + + if (ctx.Context.Version == null) + { + if (ClientCtx.Version == null) + { + throw new Exception("Version is not initialized."); + } + + ctx.Context.Version = ClientCtx.Version; + } + CallInvoker? callInvoker = null; if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0) { diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index a3a7f81..500a9c2 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -45,6 +45,6 @@ public interface IFrostFSClient : IDisposable #endregion #region Tools - ObjectId CalculateObjectId(ObjectHeader header); + ObjectId CalculateObjectId(ObjectHeader header, Context ctx); #endregion } diff --git a/src/FrostFS.SDK.ModelsV2/Client/Context.cs b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs similarity index 54% rename from src/FrostFS.SDK.ModelsV2/Client/Context.cs rename to src/FrostFS.SDK.ClientV2/Parameters/Context.cs index 4ac90a8..ca3728a 100644 --- a/src/FrostFS.SDK.ModelsV2/Client/Context.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; using System.Threading; using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.Cryptography; +using Google.Protobuf; using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; @@ -10,8 +13,18 @@ public class Context() { private List? interceptors; + private ByteString publicKeyCache; + + public ECDsa Key { get; set; } + + public OwnerId OwnerId { get; set; } + + public ModelsV2.Version Version { get; set; } + public CancellationToken CancellationToken { get; set; } = default; + public TimeSpan Timeout { get; set; } = default; + public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; public Action? Callback { get; set; } @@ -20,5 +33,18 @@ public class Context() { get { return this.interceptors ??= []; } set { this.interceptors = value; } - } + } + + public ByteString PublicKeyCache + { + get + { + if (publicKeyCache == null) + { + publicKeyCache = ByteString.CopyFrom(Key.PublicKey()); + } + + return publicKeyCache; + } + } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs b/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs new file mode 100644 index 0000000..a4915c3 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs @@ -0,0 +1,11 @@ +using FrostFS.SDK.ModelsV2; +using System.Security.Cryptography; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public class Credentials(ECDsa key, OwnerId ownerId) +{ + public ECDsa Key { get; } = key; + + public OwnerId OwnerId { get; } = ownerId; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs new file mode 100644 index 0000000..09ca363 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs @@ -0,0 +1,14 @@ +using System.Collections.Specialized; + +namespace FrostFS.SDK.ClientV2.Parameters; + +public class PrmBase() : IContext +{ + /// + /// FrostFS request X-Headers + /// + public NameValueCollection XHeaders { get; set; } = []; + + /// + public Context? Context { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index 4185a41..21e4931 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -1,8 +1,8 @@ -using System.Collections.Specialized; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmContainerCreate(ModelsV2.Container container) : IContext +public sealed class PrmContainerCreate(ModelsV2.Container container) : PrmBase, ISessionToken { public ModelsV2.Container Container { get; set; } = container; @@ -12,11 +12,7 @@ public sealed class PrmContainerCreate(ModelsV2.Container container) : IContext /// Rules for polling the result public PrmWait? WaitParams { get; set; } - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; + public string SessionKey { get; set; } - /// - public Context? Context { get; set; } + public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs index 8455f51..63308aa 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs @@ -1,9 +1,8 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = null) : IContext +public sealed class PrmContainerDelete(ContainerId containerId) : PrmBase, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; @@ -12,12 +11,6 @@ public sealed class PrmContainerDelete(ContainerId containerId, Context? ctx = n /// /// Rules for polling the result public PrmWait? WaitParams { get; set; } - - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - /// - public Context? Context { get; set; } = ctx; + public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs index 2761ba2..cebdaca 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs @@ -1,17 +1,8 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmContainerGet(ContainerId containerId, Context? ctx = null) : IContext +public sealed class PrmContainerGet(ContainerId containerId) : PrmBase { public ContainerId ContainerId { get; set; } = containerId; - - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = ctx; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index 77d954e..e155606 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -2,13 +2,6 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmContainerGetAll() : IContext +public sealed class PrmContainerGetAll() : PrmBase() { - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index 117ab85..e5316ee 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -2,13 +2,6 @@ namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmNetmapSnapshot(Context? context = default) : IContext -{ - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = context; +public sealed class PrmNetmapSnapshot() : PrmBase +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs index 250eb04..c82db6e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs @@ -1,14 +1,5 @@ -using System.Collections.Specialized; +namespace FrostFS.SDK.ClientV2.Parameters; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmNetworkSettings(Context? context = default) : IContext -{ - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = context; +public sealed class PrmNetworkSettings() : PrmBase +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs index 8a293a5..2b3b661 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs @@ -1,14 +1,5 @@ -using System.Collections.Specialized; +namespace FrostFS.SDK.ClientV2.Parameters; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmNodeInfo(Context? context = default) : IContext -{ - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = context; +public sealed class PrmNodeInfo() : PrmBase +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs index 100f558..96df2c1 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs @@ -3,19 +3,12 @@ using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } - /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs index 9c8cb1c..07e717f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs @@ -1,21 +1,13 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } - /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index 5184fe8..cdc75cb 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -1,21 +1,13 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : IContext, ISessionToken +public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken { public ContainerId ContainerId { get; set; } = containerId; + public ObjectId ObjectId { get; set; } = objectId; - - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } - + /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index fea3bd5..0fcf91d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -1,10 +1,10 @@ -using FrostFS.SDK.ModelsV2; using System.Collections.Specialized; using System.IO; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmObjectPut : IContext, ISessionToken +public sealed class PrmObjectPut : PrmBase, ISessionToken { /// /// Need to provide values like ContainerId and ObjectType to create and object. @@ -30,21 +30,13 @@ public sealed class PrmObjectPut : IContext, ISessionToken /// Overrides default size of the buffer for stream transferring. /// /// Size of the buffer - public int BufferMaxSize { get; set; } + public int BufferMaxSize { get; set; } /// /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. /// public byte[]? CustomBuffer { get; set; } - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } - /// public SessionToken? SessionToken { get; set; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index c4e8fc9..e7c56be 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; +using System.Collections.Generic; namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : IContext, ISessionToken +public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken { /// /// Defines container for the search @@ -16,15 +15,7 @@ public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilte /// /// Collection of filters public IEnumerable Filters { get; set; } = filters; - - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } - + /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs index 22f0c4e..2afc76f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs @@ -1,16 +1,6 @@ -using System.Collections.Specialized; +namespace FrostFS.SDK.ClientV2.Parameters; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmSessionCreate(ulong expiration, Context? context = default) : IContext +public sealed class PrmSessionCreate(ulong expiration) : PrmBase { public ulong Expiration { get; set; } = expiration; - - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = context; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs index 0d1df0f..fc22bf7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs @@ -1,19 +1,11 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; + namespace FrostFS.SDK.ClientV2.Parameters; -public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, Context? context = null) : IContext, ISessionToken +public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken { public FrostFsObject FrostFsObject { get; set; } = frostFsObject; - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; set; } = []; - - /// - public Context? Context { get; set; } = context; - /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 7f412c1..b91fcd1 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -17,7 +17,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { internal async Task GetContainerAsync(PrmContainerGet args) { - GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders); + GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders, args.Context!); var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken); @@ -34,12 +34,12 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { Body = new ListRequest.Types.Body { - OwnerId = Context.Owner.ToMessage() + OwnerId = ctx.OwnerId.ToMessage() } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -55,20 +55,20 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { var ctx = args.Context!; var grpcContainer = args.Container.ToMessage(); - grpcContainer.OwnerId = Context.Owner.ToMessage(); - grpcContainer.Version = Context.Version.ToMessage(); + grpcContainer.OwnerId = ctx.OwnerId.ToMessage(); + grpcContainer.Version = ctx.Version.ToMessage(); var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = Context.Key.ECDsaKey.SignRFC6979(grpcContainer) + Signature = ctx.Key.SignRFC6979(grpcContainer) } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -87,13 +87,13 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient Body = new DeleteRequest.Types.Body { ContainerId = args.ContainerId.ToMessage(), - Signature = Context.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value) + Signature = ctx.Key.SignRFC6979(args.ContainerId.ToMessage().Value) } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -102,7 +102,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient Verifier.CheckResponse(response); } - private GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders) + private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx) { var request = new GetRequest { @@ -113,7 +113,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient }; request.AddMetaHeader(xHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); return request; } @@ -126,7 +126,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx) { - var request = GetContainerRequest(id, null); + var request = GetContainerRequest(id, null, ctx); async Task action() { diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index e088ba4..60c7e1f 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -49,7 +49,7 @@ internal class NetmapServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -63,7 +63,7 @@ internal class NetmapServiceProvider : ContextAccessor var request = new NetworkInfoRequest(); request.AddMetaHeader(null); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -79,7 +79,7 @@ internal class NetmapServiceProvider : ContextAccessor var request = new NetmapSnapshotRequest(); request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 26ba3e8..95106b7 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -46,11 +46,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Head, - Context.Key.ECDsaKey); + ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -80,11 +80,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Get, - Context.Key.ECDsaKey); + ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); return await GetObject(request, ctx); } @@ -109,10 +109,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Delete, - Context.Key.ECDsaKey); + ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -139,11 +139,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( new Address { ContainerId = request.Body.ContainerId }, ObjectSessionContext.Types.Verb.Search, - Context.Key.ECDsaKey); + ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var objectsIds = SearchObjects(request, ctx); @@ -177,7 +177,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) { var ctx = args.Context!; - var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, env); + var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx); var request = new PutSingleRequest { @@ -192,11 +192,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId}, ObjectSessionContext.Types.Verb.Put, - Context.Key.ECDsaKey); + ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -226,7 +226,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C if (args.MaxObjectSizeCache == 0) { - var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }); args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; } @@ -331,7 +331,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C while (objectLimitSize == 0 || sentBytes < objectLimitSize) { - // send chanks limited to default or user's settings + // send chunks limited to default or user's settings var bufferSize = objectLimitSize > 0 ? (int)Math.Min(objectLimitSize - sentBytes, chunkSize) : chunkSize; @@ -350,8 +350,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount) } }; - - chunkRequest.Sign(Context.Key.ECDsaKey); + + chunkRequest.Sign(ctx.Key); await stream.Write(chunkRequest); } @@ -374,14 +374,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { var header = args.Header!; - header.OwnerId = Context.Owner; - header.Version = Context.Version; + header.OwnerId = ctx.OwnerId; + header.Version = ctx.Version; var grpcHeader = header.ToMessage(); if (header.Split != null) { - ObjectTools.SetSplitValues(grpcHeader, header.Split, env); + ObjectTools.SetSplitValues(grpcHeader, header.Split, ctx); } var oid = new ObjectID { Value = grpcHeader.Sha256() }; @@ -402,12 +402,12 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid }, ObjectSessionContext.Types.Verb.Put, - Context.Key.ECDsaKey + ctx.Key ); initRequest.AddMetaHeader(args.XHeaders, sessionToken); - initRequest.Sign(Context.Key.ECDsaKey); + initRequest.Sign(ctx.Key); return await PutObjectInit(initRequest, ctx); } diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 0bb5499..104fa4c 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -17,17 +17,18 @@ internal class SessionServiceProvider : ContextAccessor internal async Task CreateSessionAsync(PrmSessionCreate args) { + var ctx = args.Context!; var request = new CreateRequest { Body = new CreateRequest.Types.Body { - OwnerId = Context.Owner.ToMessage(), + OwnerId = ctx.OwnerId.ToMessage(), Expiration = args.Expiration } }; request.AddMetaHeader(args.XHeaders); - request.Sign(Context.Key.ECDsaKey); + request.Sign(ctx.Key); return await CreateSession(request, args.Context!); } diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index 05e5723..ccfec71 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -15,7 +15,7 @@ internal class SessionProvider(ClientEnvironment env) { if (args.SessionToken is null) { - return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)); + return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }); } return new Session.SessionToken().Deserialize(args.SessionToken.Token); diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 4f4fa1b..ec06eab 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,25 +1,26 @@ using FrostFS.SDK.ModelsV2; -using Google.Protobuf; using Grpc.Net.Client; using System; using System.Security.Cryptography; using FrostFS.SDK.Cryptography; -using System.Buffers; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(Client client, ECDsa key, OwnerId owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +public class ClientEnvironment(Client client, ECDsa? key, OwnerId? owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable { private ArrayPool _arrayPool; - internal OwnerId Owner { get; } = owner; + internal OwnerId? Owner { get; } = owner; + internal GrpcChannel Channel { get; private set; } = channel; + internal ModelsV2.Version Version { get; } = version; + internal NetworkSettings? NetworkSettings { get; set; } internal Client Client { get; } = client; - internal ClientKey Key { get; } = new ClientKey(key); + internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null; /// /// Custom pool is used for predefined sizes of buffers like grpc chunk diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index aad073b..321724a 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -7,26 +7,25 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; -using static FrostFS.Object.Header.Types; namespace FrostFS.SDK.ClientV2; internal class ObjectTools { - internal static ObjectId CalculateObjectId(ObjectHeader header, ClientEnvironment env) + internal static ObjectId CalculateObjectId(ObjectHeader header, Context ctx) { - var grpcHeader = CreateHeader(header, [], env); + var grpcHeader = CreateHeader(header, [], ctx); if (header.Split != null) - SetSplitValues(grpcHeader, header.Split, env); + SetSplitValues(grpcHeader, header.Split, ctx); return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal static Object.Object CreateObject(FrostFsObject @object, ClientEnvironment env) + internal static Object.Object CreateObject(FrostFsObject @object, Context ctx) { - @object.Header.OwnerId = env.Owner; - @object.Header.Version = env.Version; + @object.Header.OwnerId = ctx.OwnerId; + @object.Header.Version = ctx.Version; var grpcHeader = @object.Header.ToMessage(); @@ -36,7 +35,7 @@ internal class ObjectTools var split = @object.Header.Split; if (split != null) { - SetSplitValues(grpcHeader, split, env); + SetSplitValues(grpcHeader, split, ctx); } var obj = new Object.Object @@ -48,14 +47,14 @@ internal class ObjectTools obj.Signature = new Refs.Signature { - Key = env.Key.PublicKeyProto, - Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(obj.ObjectId.ToByteArray())), + Key = ctx.PublicKeyCache, + Sign = ByteString.CopyFrom(ctx.Key.SignData(obj.ObjectId.ToByteArray())), }; return obj; } - internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, ClientEnvironment env) + internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, Context ctx) { grpcHeader.Split = new Header.Types.Split { @@ -67,14 +66,14 @@ internal class ObjectTools if (split.ParentHeader is not null) { - var grpcParentHeader = CreateHeader(split.ParentHeader, [], env); + var grpcParentHeader = CreateHeader(split.ParentHeader, [], ctx); grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Refs.Signature { - Key = env.Key.PublicKeyProto, - Sign = ByteString.CopyFrom(env.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray())), + Key = ctx.PublicKeyCache, + Sign = ByteString.CopyFrom(ctx.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), }; split.Parent = grpcHeader.Split.Parent.ToModel(); @@ -83,12 +82,12 @@ internal class ObjectTools grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(ObjectHeader header, byte[]? payload, ClientEnvironment env) + internal static Header CreateHeader(ObjectHeader header, byte[]? payload, Context ctx) { var grpcHeader = header.ToMessage(); - grpcHeader.OwnerId = env.Owner.ToMessage(); - grpcHeader.Version = env.Version.ToMessage(); + grpcHeader.OwnerId = ctx.OwnerId.ToMessage(); + grpcHeader.Version = ctx.Version.ToMessage(); if (payload != null) // && payload.Length > 0 grpcHeader.PayloadHash = Sha256Checksum(payload); diff --git a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs index 4268d53..049d3f7 100644 --- a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs @@ -1,28 +1,64 @@ +using Google.Protobuf; using System; +using System.Collections.Generic; using System.Text; namespace FrostFS.SDK.ModelsV2; public class ClientSettings { - private static readonly string errorTemplate = "{0} is required parameter"; - - public string Key { get; set; } = string.Empty; + protected static readonly string errorTemplate = "{0} is required parameter"; public string Host { get; set; } = string.Empty; - public void Validate() + public virtual void Validate() { - StringBuilder? error = null; - - if (string.IsNullOrWhiteSpace(Key)) - (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Key))); - - if (string.IsNullOrWhiteSpace(Host)) - (error ??= new StringBuilder()).AppendLine(string.Format(errorTemplate, nameof(Host))); - - if (error != null) - throw new ArgumentException(error.ToString()); + var errors = CheckFields(); + if (errors != null) + ThrowException(errors); } + protected List? CheckFields() + { + List? errors = null; + + if (string.IsNullOrWhiteSpace(Host)) + (errors ??= []).Add(string.Format(errorTemplate, nameof(Host))); + + return errors; + } + + protected static void ThrowException(List errors) + { + StringBuilder messages = new(); + + foreach (var error in errors) + { + messages.AppendLine(error); + } + + throw new ArgumentException(messages.ToString()); + } +} + +public class SingleOwnerClientSettings : ClientSettings +{ + public string Key { get; set; } = string.Empty; + + public override void Validate() + { + var errors = CheckFields(); + if (errors != null) + ThrowException(errors); + } + + protected List? CheckFields() + { + List? errors = base.CheckFields(); + + if (string.IsNullOrWhiteSpace(Key)) + (errors ??= []).Add(string.Format(errorTemplate, nameof(Key))); + + return errors; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 74f80e3..0af0e70 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -16,12 +16,12 @@ public abstract class ContainerTestsBase { protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + protected IOptions Settings { get; set; } protected ContainerMocker Mocker { get; set; } protected ContainerTestsBase() { - Settings = Options.Create(new ClientSettings + Settings = Options.Create(new SingleOwnerClientSettings { Key = key, Host = "http://localhost:8080" diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 6fb3f0a..30ec8f0 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -19,7 +19,7 @@ public abstract class ObjectTestsBase { protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + protected IOptions Settings { get; set; } protected ContainerId ContainerId { get; set; } protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); @@ -31,7 +31,7 @@ public abstract class ObjectTestsBase { var ecdsaKey = key.LoadWif(); - Settings = Options.Create(new ClientSettings + Settings = Options.Create(new SingleOwnerClientSettings { Key = key, Host = "http://localhost:8080" @@ -71,14 +71,17 @@ public class ObjectTest : ObjectTestsBase public async void GetObjectTest() { var client = GetClient(); - var objectId = client.CalculateObjectId(Mocker.ObjectHeader!); - var context = new Context - { - Timeout = TimeSpan.FromSeconds(2) - }; + var ecdsaKey = key.LoadWif(); - var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = context }); + var ctx = new Context { + Key = ecdsaKey, + OwnerId = OwnerId.FromKey(ecdsaKey), + Version = new ModelsV2.Version(2, 13) }; + + var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx); + + var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx }); Assert.NotNull(result); diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index 8b89531..aff3298 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -13,21 +13,46 @@ using System.Diagnostics; using static FrostFS.Session.SessionToken.Types.Body; using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.Tests; namespace FrostFS.SDK.SmokeTests; -public class SmokeTests +public abstract class SmokeTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + protected readonly string url = "http://172.23.32.4:8080"; + + protected ECDsa Key { get; } + + protected OwnerId OwnerId { get; } + + protected ModelsV2.Version Version { get; } + + protected Context Ctx { get; } + + protected SmokeTestsBase() + { + Key = key.LoadWif(); + OwnerId = OwnerId.FromKey(Key); + Version = new ModelsV2.Version(2, 13); + + Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version }; + } +} + +public class SmokeTests : SmokeTestsBase { 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() - { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var result = await client.GetNetmapSnapshotAsync(); + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NetworkMapTest(bool isSingleOnwerClient) + { + using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); + + PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new () { Context = Ctx }; + var result = await client.GetNetmapSnapshotAsync(prm); Assert.True(result.Epoch > 0); Assert.Single(result.NodeInfoCollection); @@ -41,12 +66,17 @@ public class SmokeTests Assert.Equal(9, item.Attributes.Count); } - [Fact] - public async void NodeInfoTest() - { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); - var result = await client.GetNodeInfoAsync(); + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NodeInfoTest(bool isSingleOnwerClient) + { + using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); + + PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; + + var result = await client.GetNodeInfoAsync(prm); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); @@ -64,25 +94,25 @@ public class SmokeTests Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") }; - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); var result = await client.GetNodeInfoAsync(); } - [Fact] - public async void GetSessionTest() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void GetSessionTest(bool isSingleOnwerClient) { - var ecdsaKey = this.key.LoadWif(); + using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx }; - var token = await client.CreateSessionAsync(new PrmSessionCreate(100)); + var token = await client.CreateSessionAsync(prm); var session = new Session.SessionToken().Deserialize(token.Token); - var owner = OwnerId.FromKey(ecdsaKey); - - var ownerHash = Base58.Decode(owner.Value); + var ownerHash = Base58.Decode(OwnerId.Value); Assert.NotNull(session); Assert.Null(session.Body.Container); @@ -97,7 +127,7 @@ public class SmokeTests [Fact] public async void CreateObjectWithSessionToken() { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); await Cleanup(client); @@ -144,7 +174,7 @@ public class SmokeTests [Fact] public async void FilterTest() { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); await Cleanup(client); @@ -230,7 +260,7 @@ public class SmokeTests [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); await Cleanup(client); @@ -253,7 +283,7 @@ public class SmokeTests var containerId = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); Assert.NotNull(container); Assert.True(callbackInvoked); @@ -318,7 +348,7 @@ public class SmokeTests [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); @@ -338,7 +368,7 @@ public class SmokeTests var containerId = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(containerId,ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); Assert.NotNull(container); var bytes = GetRandomBytes(objectSize); @@ -406,7 +436,7 @@ public class SmokeTests [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { - using var client = Client.GetInstance(GetOptions(this.key, this.url)); + using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); await Cleanup(client); @@ -417,13 +447,13 @@ public class SmokeTests var containerId = await client.CreateContainerAsync(createContainerParam); - var context = new Context + var ctx = new Context { Timeout = TimeSpan.FromSeconds(10), Interceptors = new([new MetricsInterceptor()]) }; - var container = await client.GetContainerAsync(new PrmContainerGet(containerId, context)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); Assert.NotNull(container); @@ -499,11 +529,19 @@ public class SmokeTests return bytes; } - private static IOptions GetOptions(string key, string url) + private static IOptions GetSingleOwnerOptions(string key, string url) + { + return Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = url + }); + } + + private static IOptions GetOptions(string url) { return Options.Create(new ClientSettings { - Key = key, Host = url }); } From 22e2a535510dfb4cb599cfabdda54bcf98fb6c9f Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 16 Aug 2024 12:09:17 +0300 Subject: [PATCH 18/65] [#16] Client: Unit tests Signed-off-by: Pavel Gross --- .../Tools/ClientEnvironment.cs | 1 + .../ContainerServiceBase.cs | 6 + src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 83 ++++- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 49 +-- src/FrostFS.SDK.Tests/NetworkTest.cs | 303 ++++++++++++++++++ src/FrostFS.SDK.Tests/SessionTests.cs | 127 ++++++++ 6 files changed, 544 insertions(+), 25 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/NetworkTest.cs create mode 100644 src/FrostFS.SDK.Tests/SessionTests.cs diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index ec06eab..6fe2f15 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -3,6 +3,7 @@ using Grpc.Net.Client; using System; using System.Security.Cryptography; using FrostFS.SDK.Cryptography; +using System.Buffers; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 9de903c..7cf2690 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -27,6 +27,12 @@ public abstract class ServiceBase(string key) public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); + public Metadata Metadata { get; protected set; } + public DateTime? DateTime { get; protected set; } + public CancellationToken CancellationToken { get; protected set; } + + public CancellationTokenSource CancellationTokenSource { get; protected set; } = new CancellationTokenSource(); + public static Metadata ResponseMetaData => []; protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs index f078df6..ace5c9f 100644 --- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -3,24 +3,40 @@ using FrostFS.Netmap; using Grpc.Core; using FrostFS.SDK.ClientV2; using Google.Protobuf; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.Tests; public class NetworkMocker(string key) : ServiceBase(key) { - private static readonly string[] parameterKeys = [ + internal static readonly string[] ParameterKeys = [ + "AuditFee", + "BasicIncomeRate", "ContainerFee", + "ContainerAliasFee", "EpochDuration", - "IRCandidateFee", + "InnerRingCandidateFee", "MaxECDataCount", "MaxECParityCount", "MaxObjectSize", - "WithdrawalFee", + "WithdrawFee", "HomomorphicHashingDisabled", - "MaintenanceModeAllowed" ]; + "MaintenanceModeAllowed" + ]; public Dictionary? Parameters { get; set; } + public LocalNodeInfoResponse NodeInfoResponse { get; set; } + + public LocalNodeInfoRequest LocalNodeInfoRequest { get; set; } + + public NetworkInfoRequest NetworkInfoRequest { get; set; } + + public NetmapSnapshotResponse NetmapSnapshotResponse { get; set; } + + public NetmapSnapshotRequest NetmapSnapshotRequest { get; set; } + + public Mock GetMock() { var mock = new Mock(); @@ -29,7 +45,7 @@ public class NetworkMocker(string key) : ServiceBase(key) var networkConfig = new NetworkConfig(); - foreach (var key in parameterKeys) + foreach (var key in ParameterKeys) { networkConfig.Parameters.Add(new NetworkConfig.Types.Parameter { @@ -62,7 +78,10 @@ public class NetworkMocker(string key) : ServiceBase(key) It.IsAny())) .Returns((NetworkInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) => { - Verifier.CheckRequest(r); + NetworkInfoRequest = r; + Metadata = m; + DateTime = dt; + CancellationToken = ct; return new AsyncUnaryCall( Task.FromResult(response), @@ -72,6 +91,58 @@ public class NetworkMocker(string key) : ServiceBase(key) () => { }); }); + if (NodeInfoResponse != null) + { + NodeInfoResponse.MetaHeader = ResponseMetaHeader; + NodeInfoResponse.VerifyHeader = GetResponseVerificationHeader(NodeInfoResponse); + + mock.Setup(x => x.LocalNodeInfoAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((LocalNodeInfoRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + LocalNodeInfoRequest = r; + Metadata = m; + DateTime = dt; + CancellationToken = ct; + + return new AsyncUnaryCall( + Task.FromResult(NodeInfoResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + + if (NetmapSnapshotResponse != null) + { + NetmapSnapshotResponse.MetaHeader = ResponseMetaHeader; + NetmapSnapshotResponse.VerifyHeader = GetResponseVerificationHeader(NetmapSnapshotResponse); + + mock.Setup(x => x.NetmapSnapshotAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((NetmapSnapshotRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + NetmapSnapshotRequest = r; + Metadata = m; + DateTime = dt; + CancellationToken = ct; + + return new AsyncUnaryCall( + Task.FromResult(NetmapSnapshotResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + } + return mock; } } diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index 094090d..e1800b6 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -1,8 +1,7 @@ -using Moq; using FrostFS.Session; -using Grpc.Core; -using FrostFS.SDK.ClientV2; using Google.Protobuf; +using Grpc.Core; +using Moq; namespace FrostFS.SDK.Tests; @@ -11,24 +10,33 @@ public class SessionMocker(string key) : ServiceBase(key) public byte[]? SessionId { get; set; } public byte[]? SessionKey { get; set; } - + + public CreateRequest CreateSessionRequest { get; private set; } + public Mock GetMock() { var mock = new Mock(); Random rand = new(); - SessionId = new byte[32]; - SessionKey = new byte[32]; - rand.NextBytes(SessionId); - rand.NextBytes(SessionKey); + if (SessionId == null) + { + SessionId = new byte[32]; + rand.NextBytes(SessionId); + } + + if (SessionKey == null) + { + SessionKey = new byte[32]; + rand.NextBytes(SessionKey); + } CreateResponse response = new() { Body = new CreateResponse.Types.Body { Id = ByteString.CopyFrom(SessionId), - SessionKey = ByteString.CopyFrom(SessionId) + SessionKey = ByteString.CopyFrom(SessionKey) }, MetaHeader = ResponseMetaHeader }; @@ -40,17 +48,20 @@ public class SessionMocker(string key) : ServiceBase(key) It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) => - { - Verifier.CheckRequest(r); + .Returns((CreateRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + CreateSessionRequest = r; + Metadata = m; + DateTime = dt; + CancellationToken = ct; - return new AsyncUnaryCall( - Task.FromResult(response), - Task.FromResult(ResponseMetaData), - () => new Grpc.Core.Status(StatusCode.OK, string.Empty), - () => ResponseMetaData, - () => { }); - }); + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); return mock; } diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs new file mode 100644 index 0000000..35959d3 --- /dev/null +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -0,0 +1,303 @@ +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Enums; +using Google.Protobuf; +using Microsoft.Extensions.Options; +using System.Security.Cryptography; +using System.Threading; + +namespace FrostFS.SDK.Tests; + +public abstract class NetworkTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + + protected ModelsV2.Version Version { get; set; } = new ModelsV2.Version(2, 13); + + protected ECDsa ECDsaKey { get; set; } + protected OwnerId OwnerId { get; set; } + protected NetworkMocker Mocker { get; set; } + + protected NetworkTestsBase() + { + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + ECDsaKey = key.LoadWif(); + OwnerId = OwnerId.FromKey(ECDsaKey); + + Mocker = new NetworkMocker(this.key); + } + + protected IFrostFSClient GetClient() + { + return ClientV2.Client.GetTestInstance( + Settings, + null, + Mocker.GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + new ContainerMocker(this.key).GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} + +public class NetworkTest : NetworkTestsBase +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NetworkSettingsTest(bool useContext) + { + Mocker.Parameters = new Dictionary + { + { "AuditFee", [1] }, + { "BasicIncomeRate", [2] }, + { "ContainerFee", [3] }, + { "ContainerAliasFee", [4] }, + { "EpochDuration", [5] }, + { "InnerRingCandidateFee", [6] }, + { "MaxECDataCount", [7] }, + { "MaxECParityCount", [8] }, + { "MaxObjectSize", [9] }, + { "WithdrawFee", [10] }, + { "HomomorphicHashingDisabled", [1] }, + { "MaintenanceModeAllowed", [1] }, + }; + + var param = new PrmNetworkSettings(); + + if (useContext) + { + param.Context = new Context + { + CancellationToken = Mocker.CancellationTokenSource.Token, + Timeout = TimeSpan.FromSeconds(20), + OwnerId = OwnerId, + Key = ECDsaKey, + Version = Version + }; + } + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient().GetNetworkSettingsAsync(param); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(Mocker.Parameters["AuditFee"], [(byte)result.AuditFee]); + Assert.Equal(Mocker.Parameters["BasicIncomeRate"], [(byte)result.BasicIncomeRate]); + Assert.Equal(Mocker.Parameters["ContainerFee"], [(byte)result.ContainerFee]); + Assert.Equal(Mocker.Parameters["ContainerAliasFee"], [(byte)result.ContainerAliasFee]); + Assert.Equal(Mocker.Parameters["EpochDuration"], [(byte)result.EpochDuration]); + Assert.Equal(Mocker.Parameters["InnerRingCandidateFee"], [(byte)result.InnerRingCandidateFee]); + Assert.Equal(Mocker.Parameters["MaxECDataCount"], [(byte)result.MaxECDataCount]); + Assert.Equal(Mocker.Parameters["MaxECParityCount"], [(byte)result.MaxECParityCount]); + Assert.Equal(Mocker.Parameters["MaxObjectSize"], [(byte)result.MaxObjectSize]); + Assert.Equal(Mocker.Parameters["WithdrawFee"], [(byte)result.WithdrawFee]); + + Assert.True(result.HomomorphicHashingDisabled); + Assert.True(result.MaintenanceModeAllowed); + + if (useContext) + { + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NetmapSnapshotTest(bool useContext) + { + var body = new NetmapSnapshotResponse.Types.Body + { + Netmap = new Netmap.Netmap { Epoch = 99 } + }; + + var nodeInfo1 = new NodeInfo + { + State = NodeInfo.Types.State.Online, + PublicKey = ByteString.CopyFrom([1, 2, 3]) + }; + + nodeInfo1.Addresses.Add("address1"); + nodeInfo1.Addresses.Add("address2"); + nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"}); + nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); + + var nodeInfo2 = new NodeInfo + { + State = NodeInfo.Types.State.Offline, + PublicKey = ByteString.CopyFrom([3,4,5]) + }; + + nodeInfo2.Addresses.Add("address3"); + nodeInfo2.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key3", Value = "value3" }); + + body.Netmap.Nodes.Add(nodeInfo1); + body.Netmap.Nodes.Add(nodeInfo2); + + Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; + + var param = new PrmNetmapSnapshot(); + + if (useContext) + { + param.XHeaders.Add("headerKey1", "headerValue1"); + param.Context = new Context + { + CancellationToken = Mocker.CancellationTokenSource.Token, + Timeout = TimeSpan.FromSeconds(20), + OwnerId = OwnerId, + Key = ECDsaKey, + Version = Version + }; + } + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient().GetNetmapSnapshotAsync(param); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(99u, result.Epoch); + Assert.Equal(2, result.NodeInfoCollection.Count); + + var node1 = result.NodeInfoCollection[0]; + Assert.Equal(NodeState.Online, node1.State); + Assert.Equal(2, node1.Addresses.Count); + Assert.Equal("address1", node1.Addresses.ElementAt(0)); + Assert.Equal("address2", node1.Addresses.ElementAt(1)); + + Assert.Equal(2, node1.Attributes.Count); + + Assert.Equal("key1", node1.Attributes.ElementAt(0).Key); + Assert.Equal("value1", node1.Attributes.ElementAt(0).Value); + Assert.Equal("key2", node1.Attributes.ElementAt(1).Key); + Assert.Equal("value2", node1.Attributes.ElementAt(1).Value); + + var node2 = result.NodeInfoCollection[1]; + Assert.Equal(NodeState.Offline, node2.State); + Assert.Single(node2.Addresses); + Assert.Equal("address3", node2.Addresses.ElementAt(0)); + + Assert.Single(node2.Attributes); + + Assert.Equal("key3", node2.Attributes.ElementAt(0).Key); + Assert.Equal("value3", node2.Attributes.ElementAt(0).Value); + + if (useContext) + { + Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); + Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key); + Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value); + + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NodeInfoTest(bool useContext) + { + var body = new LocalNodeInfoResponse.Types.Body + { + NodeInfo = new NodeInfo() + { + State = NodeInfo.Types.State.Online, + PublicKey = ByteString.CopyFrom([1, 2, 3]) + }, + Version = new Refs.Version { Major = 2, Minor = 12 } + }; + + body.NodeInfo.Addresses.Add("address1"); + body.NodeInfo.Addresses.Add("address2"); + body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"}); + body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); + + Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; + + var param = new PrmNodeInfo(); + + if (useContext) + { + param.XHeaders.Add("headerKey1", "headerValue1"); + param.Context = new Context + { + CancellationToken = Mocker.CancellationTokenSource.Token, + Timeout = TimeSpan.FromSeconds(20), + OwnerId = OwnerId, + Key = ECDsaKey, + Version = Version + }; + } + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient().GetNodeInfoAsync(param); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(NodeState.Online, result.State); + + Assert.Equal(2, result.Addresses.Count); + Assert.Equal("address1", result.Addresses.ElementAt(0)); + Assert.Equal("address2", result.Addresses.ElementAt(1)); + + Assert.Equal(2, result.Attributes.Count); + Assert.Equal("value1", result.Attributes["key1"]); + Assert.Equal("value2", result.Attributes["key2"]); + + if (useContext) + { + Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); + Assert.Equal(param.XHeaders.Keys[0], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Key); + Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Value); + + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs new file mode 100644 index 0000000..8b3dc1a --- /dev/null +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -0,0 +1,127 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2.Netmap; +using Microsoft.Extensions.Options; +using System.Security.Cryptography; + +namespace FrostFS.SDK.Tests; + +public abstract class SessionTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + + + protected ECDsa ECDsaKey { get; set; } + protected OwnerId OwnerId { get; set; } + protected SessionMocker Mocker { get; set; } + + protected SessionTestsBase() + { + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + ECDsaKey = key.LoadWif(); + OwnerId = OwnerId.FromKey(ECDsaKey); + + Mocker = new SessionMocker(this.key) + { + PlacementPolicy = new PlacementPolicy(true, new Replica(1)), + Version = new ModelsV2.Version(2, 13) + }; + } + + protected IFrostFSClient GetClient() + { + return ClientV2.Client.GetTestInstance( + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ContainerMocker(this.key).GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} + +public class SessionTest : SessionTestsBase +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void CreateSessionTest(bool useContext) + { + var exp = 100u; + var param = new PrmSessionCreate(exp); + + if (useContext) + { + param.XHeaders.Add("headerKey1", "headerValue1"); + param.Context = new Context + { + CancellationToken = Mocker.CancellationTokenSource.Token, + Timeout = TimeSpan.FromSeconds(20), + OwnerId = OwnerId, + Key = ECDsaKey, + Version = Mocker.Version + }; + } + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient().CreateSessionAsync(param); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + Assert.NotNull(result.Token); + + var session = new Session.SessionToken().Deserialize(result.Token); + + Assert.Equal(Mocker.SessionId, session.Body.Id); + Assert.Equal(Mocker.SessionKey, session.Body.SessionKey); + + Assert.Equal(OwnerId.ToMessage(), session.Body.OwnerId); + Assert.Equal(exp, session.Body.Lifetime.Exp); + Assert.Equal(exp, session.Body.Lifetime.Iat); + Assert.Equal(exp, session.Body.Lifetime.Nbf); + Assert.Null(session.Body.Container); + + Assert.NotNull(Mocker.CreateSessionRequest); + + Assert.Equal(OwnerId.ToMessage(), Mocker.CreateSessionRequest.Body.OwnerId); + Assert.Equal(exp, Mocker.CreateSessionRequest.Body.Expiration); + Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader); + Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version); + + + Assert.Null(Mocker.Metadata); + + if (useContext) + { + Assert.Single(Mocker.CreateSessionRequest.MetaHeader.XHeaders); + Assert.Equal(param.XHeaders.Keys[0], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Key); + Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Value); + + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + Assert.True(validTimeoutTo.Ticks >= Mocker.DateTime.Value.Ticks); + } + else + { + Assert.Empty(Mocker.CreateSessionRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } +} \ No newline at end of file From 1a02ac2ae7d278f20879ebdc2985f04e04d760f7 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 19 Aug 2024 10:48:06 +0300 Subject: [PATCH 19/65] [#22] Client: Container session Signed-off-by: Pavel Gross --- .../Parameters/PrmContainerCreate.cs | 8 ++-- .../Parameters/PrmContainerGetAll.cs | 4 +- .../Parameters/PrmNetmapSnapshot.cs | 4 +- .../Parameters/PrmObjectDelete.cs | 3 +- .../Parameters/PrmObjectHeadGet.cs | 2 +- .../Parameters/PrmObjectPut.cs | 1 - .../Parameters/PrmObjectSearch.cs | 2 +- .../Services/ContainerServiceProvider.cs | 39 ++++++++++++++++--- .../Tools/ClientEnvironment.cs | 1 - .../Tools/RequestConstructor.cs | 24 ++++++++++++ .../Client/ClientSettings.cs | 1 - src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 1 - .../Object/FrostFsObject.cs | 1 - src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 2 - src/FrostFS.SDK.Tests/NetworkTest.cs | 1 - src/FrostFS.SDK.Tests/ObjectTest.cs | 1 - 16 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index 21e4931..a2a3315 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -1,4 +1,5 @@ using FrostFS.SDK.ModelsV2; +using System.Security.Cryptography; namespace FrostFS.SDK.ClientV2.Parameters; @@ -11,8 +12,9 @@ public sealed class PrmContainerCreate(ModelsV2.Container container) : PrmBase, /// /// Rules for polling the result public PrmWait? WaitParams { get; set; } - - public string SessionKey { get; set; } - + + /// + /// Blank session token + /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index e155606..b365188 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -1,6 +1,4 @@ -using System.Collections.Specialized; - -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2.Parameters; public sealed class PrmContainerGetAll() : PrmBase() { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index e5316ee..e8387dc 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -1,6 +1,4 @@ -using System.Collections.Specialized; - -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2.Parameters; public sealed class PrmNetmapSnapshot() : PrmBase { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs index 96df2c1..3195bb8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs @@ -1,5 +1,4 @@ -using System.Collections.Specialized; -using FrostFS.SDK.ModelsV2; +using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Parameters; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index cdc75cb..f5a9b77 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -7,7 +7,7 @@ public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) public ContainerId ContainerId { get; set; } = containerId; public ObjectId ObjectId { get; set; } = objectId; - + /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index 0fcf91d..e2c7a51 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -1,4 +1,3 @@ -using System.Collections.Specialized; using System.IO; using FrostFS.SDK.ModelsV2; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index e7c56be..778b4a0 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -15,7 +15,7 @@ public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilte /// /// Collection of filters public IEnumerable Filters { get; set; } = filters; - + /// public SessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index b91fcd1..5811a3b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -10,11 +10,19 @@ using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ClientV2.Parameters; using FrostFS.Refs; +using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -internal class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context) +internal class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider { + readonly SessionProvider sessions = new(context); + + public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + { + return await sessions.GetOrCreateSession(args, ctx); + } + internal async Task GetContainerAsync(PrmContainerGet args) { GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders, args.Context!); @@ -57,7 +65,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient var grpcContainer = args.Container.ToMessage(); grpcContainer.OwnerId = ctx.OwnerId.ToMessage(); grpcContainer.Version = ctx.Version.ToMessage(); - + var request = new PutRequest { Body = new PutRequest.Types.Body @@ -67,7 +75,18 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient } }; - request.AddMetaHeader(args.XHeaders); + var sessionToken = await GetOrCreateSession(args, ctx); + + sessionToken.CreateContainerTokenContext( + null, + ContainerSessionContext.Types.Verb.Put, + ctx.Key, + ctx.PublicKeyCache); + + var v = sessionToken.Body.OwnerId == grpcContainer.OwnerId; + + request.AddMetaHeader(args.XHeaders, sessionToken); + request.Sign(ctx.Key); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -91,12 +110,22 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient } }; - request.AddMetaHeader(args.XHeaders); + var sessionToken = await GetOrCreateSession(args, ctx); - request.Sign(ctx.Key); + sessionToken.CreateContainerTokenContext( + request.Body.ContainerId, + ContainerSessionContext.Types.Verb.Delete, + ctx.Key, + ctx.PublicKeyCache); + + request.AddMetaHeader(args.XHeaders, sessionToken); + + request.Sign(ctx.Key); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + Verifier.CheckResponse(response); + await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 6fe2f15..14ccd40 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -2,7 +2,6 @@ using FrostFS.SDK.ModelsV2; using Grpc.Net.Client; using System; using System.Security.Cryptography; -using FrostFS.SDK.Cryptography; using System.Buffers; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index acced67..2bbba92 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -4,6 +4,7 @@ using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; @@ -47,6 +48,29 @@ public static class RequestConstructor Verb = verb }; + sessionToken.Body.SessionKey = Google.Protobuf.ByteString.CopyFrom(key.PublicKey()); + + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); + } + + public static void CreateContainerTokenContext(this Session.SessionToken sessionToken, + ContainerID? containerId, + ContainerSessionContext.Types.Verb verb, + ECDsa key, + Google.Protobuf.ByteString publicKey) + { + if (sessionToken.Body.Container?.ContainerId != null) + return; + + sessionToken.Body.Container = new (){ Verb = verb }; + + if (containerId != null) + sessionToken.Body.Container.ContainerId = containerId; + else + sessionToken.Body.Container.Wildcard = true; + + sessionToken.Body.SessionKey = publicKey; + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); } } diff --git a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs index 049d3f7..c2abf26 100644 --- a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs @@ -1,4 +1,3 @@ -using Google.Protobuf; using System; using System.Collections.Generic; using System.Text; diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs index 0b7f3bd..872038b 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs @@ -1,6 +1,5 @@ using FrostFS.SDK.Cryptography; using System; -using System.Security.Cryptography; namespace FrostFS.SDK.ModelsV2; diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index 971d41d..1443abf 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ModelsV2; diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs index ace5c9f..8a63410 100644 --- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -1,9 +1,7 @@ using Moq; using FrostFS.Netmap; using Grpc.Core; -using FrostFS.SDK.ClientV2; using Google.Protobuf; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 35959d3..0fce906 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -8,7 +8,6 @@ using FrostFS.SDK.ModelsV2.Enums; using Google.Protobuf; using Microsoft.Extensions.Options; using System.Security.Cryptography; -using System.Threading; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 30ec8f0..db20741 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -7,7 +7,6 @@ using FrostFS.SDK.Cryptography; using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ModelsV2.Enums; using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ProtosV2.Interfaces; using Google.Protobuf; using Microsoft.Extensions.Options; using System.Security.Cryptography; From 6562aa27a56d3b908dae8638628a83b1c1b8bafd Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 11 Sep 2024 10:44:30 +0300 Subject: [PATCH 20/65] [#23] Client: Refactoring to optimize memory usage Signed-off-by: Pavel Gross --- FrostFS.SDK.sln | 11 +- README.md | 175 +++++------------ src/FrostFS.SDK.ClientV2/Cache.cs | 4 +- src/FrostFS.SDK.ClientV2/Client.cs | 70 +++---- src/FrostFS.SDK.ClientV2/CllientKey.cs | 6 +- .../Exceptions/InvalidObjectException.cs | 7 + .../Exceptions/ResponseException.cs | 5 +- .../FrostFS.SDK.ClientV2.csproj | 1 - .../Interceptors/MetricsInterceptor.cs | 2 +- .../Interfaces/IFrostFSClient.cs | 27 ++- src/FrostFS.SDK.ClientV2/Mappers/Container.cs | 28 +-- .../Mappers/ContainerId.cs | 15 +- .../Mappers/MetaHeader.cs | 1 - .../Mappers/Netmap/Netmap.cs | 10 +- .../Mappers/Netmap/NodeInfo.cs | 22 +-- .../Mappers/Netmap/PlacementPolicy.cs | 9 +- .../Mappers/Netmap/Replica.cs | 8 +- .../Mappers/Object/Object.cs | 4 +- .../Mappers/Object/ObjectAttributeMapper.cs | 7 +- .../Mappers/Object/ObjectFilterMapper.cs | 13 +- .../Mappers/Object/ObjectHeaderMapper.cs | 74 ++----- .../Mappers/Object/ObjectId.cs | 8 +- src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs | 14 +- .../Mappers/SignatureMapper.cs | 4 +- src/FrostFS.SDK.ClientV2/Mappers/Status.cs | 11 +- src/FrostFS.SDK.ClientV2/Mappers/Version.cs | 15 +- .../Models/Client/ClientSettings.cs | 63 ++++++ .../Models/Containers/FrostFsContainerId.cs | 62 ++++++ .../Models/Containers/FrostFsContainerInfo.cs | 107 ++++++++++ .../Models/Enums/BasicAcl.cs | 21 ++ .../Models/Enums/FrostFsObjectMatchType.cs} | 4 +- .../Models/Enums/FrostFsObjectType.cs | 8 + .../Models/Enums/FrostFsStatusCode.cs} | 4 +- .../Models/Enums/NodeState.cs | 9 + .../Models/Enums/SignatureScheme.cs | 8 + .../Models/Misc/CallStatistics.cs | 7 + .../Models/Misc/CheckSum.cs | 20 ++ .../Models/Misc/Constants.cs | 52 +++++ .../Models/Netmap/FrostFsNetmapSnapshot.cs | 10 + .../Models/Netmap/FrostFsNodeInfo.cs} | 9 +- .../Models/Netmap/FrostFsPlacementPolicy.cs} | 10 +- .../Models/Netmap/FrostFsReplica.cs} | 8 +- .../Models/Netmap/FrostFsVersion.cs | 31 +++ .../Models/Object/FrostFsAttribute.cs | 8 + .../Models/Object/FrostFsLargeObject.cs | 9 + .../Models/Object/FrostFsLinkObject.cs | 13 ++ .../Models/Object/FrostFsObject.cs | 64 ++++++ .../Models/Object/FrostFsObjectFilter.cs | 111 +++++++++++ .../Models/Object/FrostFsObjectHeader.cs | 85 ++++++++ .../Models/Object/FrostFsObjectId.cs | 28 +++ .../Models/Object/FrostFsOwner.cs | 38 ++++ .../Models/Object/FrostFsSplit.cs | 24 +++ .../Models/Object/IObjectReader.cs | 10 + .../Models/Object/SplitId.cs | 62 ++++++ .../Models/Response/FrostFsResponseStatus.cs | 14 ++ .../Models/Response/FrostFsSignature.cs | 10 + .../Models/Response/MetaHeader.cs | 20 ++ .../Models/Session/FrostFsSessionToken.cs | 6 + .../Parameters/Context.cs | 23 ++- .../Parameters/Credentials.cs | 9 +- .../Parameters/IContext.cs | 2 +- .../Parameters/ISessionToken.cs | 6 +- .../Parameters/PrmBase.cs | 2 +- .../Parameters/PrmContainerCreate.cs | 11 +- .../Parameters/PrmContainerDelete.cs | 10 +- .../Parameters/PrmContainerGet.cs | 8 +- .../Parameters/PrmContainerGetAll.cs | 2 +- .../Parameters/PrmNetmapSnapshot.cs | 2 +- .../Parameters/PrmNetworkSettings.cs | 2 +- .../Parameters/PrmNodeInfo.cs | 2 +- .../Parameters/PrmObjectDelete.cs | 12 +- .../Parameters/PrmObjectGet.cs | 12 +- .../Parameters/PrmObjectHeadGet.cs | 12 +- .../Parameters/PrmObjectPut.cs | 7 +- .../Parameters/PrmObjectSearch.cs | 12 +- .../Parameters/PrmSessionCreate.cs | 2 +- .../Parameters/PrmSingleObjectPut.cs | 6 +- .../Parameters/PrmWait.cs | 3 +- .../Services/ContainerServiceProvider.cs | 39 ++-- .../Services/NetmapServiceProvider.cs | 12 +- .../Services/ObjectServiceProvider.cs | 60 +++--- .../Services/SessionServiceProvider.cs | 3 +- .../Services/Shared/ContextAccessor.cs | 2 - .../Services/Shared/SessionProvider.cs | 4 +- .../Tools/ClientEnvironment.cs | 14 +- src/FrostFS.SDK.ClientV2/Tools/Object.cs | 12 +- .../Tools/ObjectReader.cs | 7 +- .../Tools/ObjectStreamer.cs | 2 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 30 +-- src/FrostFS.SDK.ClientV2/Tools/Range.cs | 35 +--- .../Tools/RequestConstructor.cs | 1 - .../Tools/RequestSigner.cs | 10 +- .../Tools/SearchReader.cs | 8 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 12 +- .../Client/ClientSettings.cs | 10 +- .../Containers/Container.cs | 13 -- .../Containers/ContainerId.cs | 2 +- .../Containers/FrostFsContainer.cs | 11 ++ src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs | 2 +- .../Enums/FrostFsObjectMatchType.cs | 10 + .../Enums/FrostFsObjectType.cs | 8 + .../Enums/FrostFsStatusCode.cs | 22 +++ src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs | 2 +- src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs | 8 - .../Enums/SignatureScheme.cs | 7 +- .../Misc/CallStatistics.cs | 4 +- src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 2 +- src/FrostFS.SDK.ModelsV2/Misc/Constants.cs | 2 +- .../Netmap/FrostFsNodeInfo.cs | 18 ++ .../Netmap/FrostFsPlacementPolicy.cs | 28 +++ .../Netmap/FrostFsReplica.cs | 15 ++ .../Netmap/{Version.cs => FrostFsVersion.cs} | 6 +- src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs | 6 +- .../Object/FrostFsObject.cs | 19 +- .../Object/IObjectReader.cs | 4 +- .../Object/ObjectAttribute.cs | 2 +- .../Object/ObjectFilter.cs | 38 ++-- .../Object/ObjectHeader.cs | 9 +- src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs | 4 +- src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs | 2 +- src/FrostFS.SDK.ModelsV2/Object/SplitId.cs | 30 ++- src/FrostFS.SDK.ModelsV2/Object/Splitter.cs | 7 +- .../Response/FrostFsResponseStatus.cs | 14 ++ .../Response/FrostFsSignature.cs | 10 + .../Response/MetaHeader.cs | 8 +- .../Response/ResponseStatus.cs | 16 -- .../Response/Signature.cs | 8 - .../Session/FrostFsSessionToken.cs | 6 + .../Session/SessionToken.cs | 6 - src/FrostFS.SDK.Tests/ContainerTest.cs | 19 +- src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 39 ++++ .../Mocks/AsyncStreamReaderMock.cs | 15 +- .../Mocks/ClientStreamWriter.cs | 5 +- .../ContainerServiceBase.cs | 12 +- .../ContainerServiceMocks/GetContainerMock.cs | 3 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 6 +- src/FrostFS.SDK.Tests/NetworkTest.cs | 13 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 36 ++-- src/FrostFS.SDK.Tests/SessionTests.cs | 15 +- src/FrostFS.SDK.Tests/SmokeTests.cs | 185 ++++++------------ src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 29 +++ 141 files changed, 1722 insertions(+), 896 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs rename src/{FrostFS.SDK.ModelsV2/Enums/ObjectMatchType.cs => FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs} (59%) create mode 100644 src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs rename src/{FrostFS.SDK.ModelsV2/Enums/StatusCode.cs => FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs} (88%) create mode 100644 src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs rename src/{FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs => FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs} (74%) rename src/{FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs => FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs} (59%) rename src/{FrostFS.SDK.ModelsV2/Netmap/Replica.cs => FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs} (60%) create mode 100644 src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Containers/Container.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs rename src/FrostFS.SDK.ModelsV2/Netmap/{Version.cs => FrostFsVersion.cs} (64%) create mode 100644 src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Response/Signature.cs create mode 100644 src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs create mode 100644 src/FrostFS.SDK.Tests/MetricsInterceptor.cs create mode 100644 src/FrostFS.SDK.Tests/SmokeTestsBase.cs diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index c192375..f0c0d87 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -1,16 +1,13 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 -# VisualStudioVersion = 17.5.002.0 - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ModelsV2", "src\FrostFS.SDK.ModelsV2\FrostFS.SDK.ModelsV2.csproj", "{14DC8AC7-E310-40C2-ACDF-5BE78FC0E1B2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,10 +23,6 @@ Global {3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D804F4A-B0B2-47A5-B006-BE447BE64B50}.Release|Any CPU.Build.0 = Release|Any CPU - {14DC8AC7-E310-40C2-ACDF-5BE78FC0E1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14DC8AC7-E310-40C2-ACDF-5BE78FC0E1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14DC8AC7-E310-40C2-ACDF-5BE78FC0E1B2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14DC8AC7-E310-40C2-ACDF-5BE78FC0E1B2}.Release|Any CPU.Build.0 = Release|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Debug|Any CPU.Build.0 = Debug|Any CPU {5012EF96-9C9E-4E77-BC78-B4111EE54107}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/README.md b/README.md index 89ab89a..d2e5fab 100644 --- a/README.md +++ b/README.md @@ -21,137 +21,62 @@ neo-go wallet export -w -d ### Container ```csharp +using FrostFS.SDK; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; -var fsClient = Client.GetInstance(, ); +using Microsoft.Extensions.Options; -// List containers -var containersIds = await fsClient.ListContainersAsync(); +var Key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; +var Host = "http://172.22.33.44:8080"; -// Create container -var placementPolicy = new PlacementPolicy(true, new Replica(1)); -var containerId = await fsClient.CreateContainerAsync( - new Container( - BasicAcl.PublicRW, - placementPolicy - ) -); - -// Get container -var container = await fsClient.GetContainerAsync(cId); - -// Delete container -await fsClient.DeleteContainerAsync(containerId); -``` - -### Object - -```csharp -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; - -var fsClient = Client.GetInstance(, ); - -// Search regular objects -var objectsIds = await fsClient.SearchObjectAsync( - cId, - ObjectFilter.RootFilter() -); - -// Put object -var f = File.OpenRead("cat.jpg"); -var cat = new ObjectHeader( - containerId: cId, - type: ObjectType.Regular, - new ObjectAttribute("Filename", "cat.jpg") -); -var oId = await fsClient.PutObjectAsync(cat, f); - -// Get object header -var objHeader = await fsClient.GetObjectHeadAsync(cId, oId); - -// Get object -var obj = await fsClient.GetObjectAsync(cId, oId); -``` - -### Custom client cut -```csharp - -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ClientV2.Extensions; -using FrostFS.SDK.ClientV2.Interfaces; - -var fsClient = Client.GetInstance(, ); - -ContainerId containerId = ; -string fileName = ; - -await PutObjectClientCut(fsClient, containerId, fileName); - -static async Task PutObjectClientCut(IFrostFSClient fsClient, ContainerId containerId, string fileName) +var options = Options.Create(new SingleOwnerClientSettings { - List sentObjectIds = []; - FrostFS.SDK.ModelsV2.Object? currentObject; + Key = Key, + Host = Host +}); - var partSize = 1024 * 1024; - var buffer = new byte[partSize]; +using var client = Client.GetSingleOwnerInstance(options); - var largeObject = new LargeObject(containerId); - - var split = new Split(); - - var fileInfo = new FileInfo(fileName); - var fullLength = (ulong)fileInfo.Length; - var fileNameAttribute = new ObjectAttribute("fileName", fileInfo.Name); - - using var stream = File.OpenRead(fileName); - while (true) - { - var bytesCount = await stream.ReadAsync(buffer.AsMemory(0, partSize)); - - split.Previous = sentObjectIds.LastOrDefault(); - - largeObject.AppendBlock(buffer, bytesCount); - - currentObject = new FrostFS.SDK.ModelsV2.Object(containerId, bytesCount < partSize ? buffer.Take(bytesCount).ToArray() : buffer) - .AddAttribute(fileNameAttribute) - .SetSplit(split); - - if (largeObject.PayloadLength == fullLength) - break; - - var objectId = await fsClient.PutSingleObjectAsync(currentObject); - sentObjectIds.Add(objectId); - } - - if (sentObjectIds.Any()) - { - largeObject.CalculateHash() - .AddAttribute(fileNameAttribute); - - currentObject.SetParent(largeObject); - - var objectId = await fsClient.PutSingleObjectAsync(currentObject); - sentObjectIds.Add(objectId); - - var linkObject = new LinkObject(containerId, split.SplitId, largeObject) - .AddChildren(sentObjectIds) - .AddAttribute(fileNameAttribute); - - _ = await fsClient.PutSingleObjectAsync(linkObject); - - return currentObject.GetParentId(); - } - - return await fsClient.PutSingleObjectAsync(currentObject); +await foreach (var cid in client.ListContainersAsync()) +{ + await client.DeleteContainerAsync(new PrmContainerDelete(cid)); } -``` \ No newline at end of file +var placementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); + +var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); + +var containerId = await client.CreateContainerAsync(createContainerParam); + +using var fileStream = File.OpenRead(@"C:\Users\Paul\Pictures\cat.jpeg"); + +var param = new PrmObjectPut +{ + Header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")]), + Payload = fileStream +}; + +FrostFsObjectId objectId = await client.PutObjectAsync(param); + +var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); + +await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] })) +{ + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId)); +} + +var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + +var downloadedBytes = new byte[@object.Header.PayloadLength]; +MemoryStream ms = new(downloadedBytes); + +ReadOnlyMemory? chunk = null; +while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) +{ + ms.Write(chunk.Value.Span); +} +``` diff --git a/src/FrostFS.SDK.ClientV2/Cache.cs b/src/FrostFS.SDK.ClientV2/Cache.cs index a41c8b9..19ec83b 100644 --- a/src/FrostFS.SDK.ClientV2/Cache.cs +++ b/src/FrostFS.SDK.ClientV2/Cache.cs @@ -1,6 +1,4 @@ - - -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Memory; namespace FrostFS.SDK.ClientV2 { diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 0142c03..6d47be5 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -1,21 +1,21 @@ -using FrostFS.Container; -using FrostFS.Netmap; -using FrostFS.Object; -using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2.Parameters; -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.Session; -using Grpc.Core; -using Grpc.Core.Interceptors; -using Grpc.Net.Client; -using Microsoft.Extensions.Options; -using System; +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; -using Version = FrostFS.SDK.ModelsV2.Version; + +using FrostFS.Container; +using FrostFS.Netmap; +using FrostFS.Object; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; +using FrostFS.Session; + +using Grpc.Core; +using Grpc.Core.Interceptors; +using Grpc.Net.Client; + +using Microsoft.Extensions.Options; namespace FrostFS.SDK.ClientV2; @@ -70,14 +70,14 @@ public class Client : IFrostFSClient ObjectService.ObjectServiceClient objectService) { var ecdsaKey = settings.Value.Key.LoadWif(); - OwnerId.FromKey(ecdsaKey); + FrostFsOwner.FromKey(ecdsaKey); ClientCtx = new ClientEnvironment( client: this, key: ecdsaKey, - owner: OwnerId.FromKey(ecdsaKey), + owner: FrostFsOwner.FromKey(ecdsaKey), channel: InitGrpcChannel(settings.Value.Host, channelOptions), - version: new Version(2, 13)); + version: new FrostFsVersion(2, 13)); ContainerServiceClient = containerService; NetmapServiceClient = netmapService; @@ -98,7 +98,7 @@ public class Client : IFrostFSClient key: null, owner: null, channel: channel, - version: new Version(2, 13)); + version: new FrostFsVersion(2, 13)); // TODO: define timeout logic // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); @@ -117,9 +117,9 @@ public class Client : IFrostFSClient ClientCtx = new ClientEnvironment( this, key: ecdsaKey, - owner: OwnerId.FromKey(ecdsaKey), + owner: FrostFsOwner.FromKey(ecdsaKey), channel: channel, - version: new Version(2, 13)); + version: new FrostFsVersion(2, 13)); // TODO: define timeout logic CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20)}); @@ -135,26 +135,26 @@ public class Client : IFrostFSClient { if (disposing && !isDisposed) { - ClientCtx.Dispose(); + ClientCtx?.Dispose(); isDisposed = true; } } #region ContainerImplementation - public Task GetContainerAsync(PrmContainerGet args) + public Task GetContainerAsync(PrmContainerGet args) { var service = GetContainerService(args); return service.GetContainerAsync(args); } - public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) + public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { args ??= new PrmContainerGetAll(); var service = GetContainerService(args); return service.ListContainersAsync(args); } - public Task CreateContainerAsync(PrmContainerCreate args) + public Task CreateContainerAsync(PrmContainerCreate args) { var service = GetContainerService(args); return service.CreateContainerAsync(args); @@ -168,14 +168,14 @@ public class Client : IFrostFSClient #endregion #region NetworkImplementation - public Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args) + public Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args) { args ??= new PrmNetmapSnapshot(); var service = GetNetmapService(args); return service.GetNetmapSnapshotAsync(args); } - public Task GetNodeInfoAsync(PrmNodeInfo? args) + public Task GetNodeInfoAsync(PrmNodeInfo? args) { args ??= new PrmNodeInfo(); var service = GetNetmapService(args); @@ -191,7 +191,7 @@ public class Client : IFrostFSClient #endregion #region ObjectImplementation - public Task GetObjectHeadAsync(PrmObjectHeadGet args) + public Task GetObjectHeadAsync(PrmObjectHeadGet args) { var service = GetObjectService(args); return service.GetObjectHeadAsync(args); @@ -203,13 +203,13 @@ public class Client : IFrostFSClient return service.GetObjectAsync(args); } - public Task PutObjectAsync(PrmObjectPut args) + public Task PutObjectAsync(PrmObjectPut args) { var service = GetObjectService(args); return service.PutObjectAsync(args); } - public Task PutSingleObjectAsync(PrmSingleObjectPut args) + public Task PutSingleObjectAsync(PrmSingleObjectPut args) { var service = GetObjectService(args); return service.PutSingleObjectAsync(args); @@ -221,7 +221,7 @@ public class Client : IFrostFSClient return service.DeleteObjectAsync(args); } - public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { var service = GetObjectService(args); return service.SearchObjectsAsync(args); @@ -229,12 +229,12 @@ public class Client : IFrostFSClient #endregion #region SessionImplementation - public async Task CreateSessionAsync(PrmSessionCreate args) + public async Task CreateSessionAsync(PrmSessionCreate args) { var session = await CreateSessionInternalAsync(args); var token = session.Serialize(); - return new ModelsV2.SessionToken(token); + return new FrostFsSessionToken(token); } internal Task CreateSessionInternalAsync(PrmSessionCreate args) @@ -245,7 +245,7 @@ public class Client : IFrostFSClient #endregion #region ToolsImplementation - public ObjectId CalculateObjectId(ObjectHeader header, Context ctx) + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx) { if (header == null) throw new ArgumentNullException(nameof(header)); @@ -286,7 +286,7 @@ public class Client : IFrostFSClient if (ctx.Context.OwnerId == null) { - ctx.Context.OwnerId = ClientCtx.Owner ?? OwnerId.FromKey(ctx.Context.Key); + ctx.Context.OwnerId = ClientCtx.Owner ?? FrostFsOwner.FromKey(ctx.Context.Key); } if (ctx.Context.Version == null) diff --git a/src/FrostFS.SDK.ClientV2/CllientKey.cs b/src/FrostFS.SDK.ClientV2/CllientKey.cs index e415a98..5b10485 100644 --- a/src/FrostFS.SDK.ClientV2/CllientKey.cs +++ b/src/FrostFS.SDK.ClientV2/CllientKey.cs @@ -1,6 +1,8 @@ -using FrostFS.SDK.Cryptography; +using System.Security.Cryptography; + +using FrostFS.SDK.Cryptography; + using Google.Protobuf; -using System.Security.Cryptography; namespace FrostFS.SDK.ClientV2 { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs new file mode 100644 index 0000000..f2a8ea7 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs @@ -0,0 +1,7 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class InvalidObjectException() : Exception() +{ +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs index d72f173..ad3c63e 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs +++ b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs @@ -1,9 +1,8 @@ -using FrostFS.SDK.ModelsV2; using System; namespace FrostFS.SDK.ClientV2; -public class ResponseException(ResponseStatus status) : Exception() +public class ResponseException(FrostFsResponseStatus status) : Exception() { - public ResponseStatus Status { get; set; } = status; + public FrostFsResponseStatus Status { get; set; } = status; } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index f811e53..5f423dd 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -21,7 +21,6 @@ - diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs index c395629..dc439f9 100644 --- a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; -using FrostFS.SDK.ModelsV2; + using Grpc.Core; using Grpc.Core.Interceptors; diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 500a9c2..80608d0 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -1,50 +1,49 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using FrostFS.SDK.ClientV2.Parameters; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Netmap; + +using FrostFS.SDK.ClientV2; namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable { #region Network - Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null); + Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null); - Task GetNodeInfoAsync(PrmNodeInfo? args = null); + Task GetNodeInfoAsync(PrmNodeInfo? args = null); Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null); #endregion #region Session - Task CreateSessionAsync(PrmSessionCreate args); + Task CreateSessionAsync(PrmSessionCreate args); #endregion #region Container - Task GetContainerAsync(PrmContainerGet args); + Task GetContainerAsync(PrmContainerGet args); - IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null); + IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null); - Task CreateContainerAsync(PrmContainerCreate args); + Task CreateContainerAsync(PrmContainerCreate args); Task DeleteContainerAsync(PrmContainerDelete args); #endregion #region Object - Task GetObjectHeadAsync(PrmObjectHeadGet args); + Task GetObjectHeadAsync(PrmObjectHeadGet args); Task GetObjectAsync(PrmObjectGet args); - Task PutObjectAsync(PrmObjectPut putObjectParameters); + Task PutObjectAsync(PrmObjectPut args); - Task PutSingleObjectAsync(PrmSingleObjectPut args); + Task PutSingleObjectAsync(PrmSingleObjectPut args); Task DeleteObjectAsync(PrmObjectDelete args); - IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); + IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); #endregion #region Tools - ObjectId CalculateObjectId(ObjectHeader header, Context ctx); + FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx); #endregion } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs index 83bfb32..181de40 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs @@ -1,36 +1,24 @@ using System; +using System.Linq; -using Google.Protobuf; - -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ContainerMapper { - public static Container.Container ToMessage(this ModelsV2.Container container) - { - return new Container.Container - { - BasicAcl = (uint)container.BasicAcl, - PlacementPolicy = container.PlacementPolicy.ToMessage(), - Nonce = ByteString.CopyFrom(container.Nonce.ToBytes()) - }; - } - - public static ModelsV2.Container ToModel(this Container.Container container) + public static FrostFsContainerInfo ToModel(this Container.Container container) { if (!Enum.IsDefined(typeof(BasicAcl),(int)container.BasicAcl)) throw new ArgumentException($"Unknown BasicACL rule. Value: '{container.BasicAcl}'."); BasicAcl acl = (BasicAcl)container.BasicAcl; - return new ModelsV2.Container(acl, container.PlacementPolicy.ToModel()) - { - Nonce = container.Nonce.ToUuid(), - Version = container.Version.ToModel() - }; + return new FrostFsContainerInfo(acl, + container.PlacementPolicy.ToModel(), + container.Attributes?.Select(a => new FrostFsAttribute(a.Key, a.Value)).ToList(), + container.Version?.ToModel(), + container.OwnerId?.ToModel(), + container.Nonce?.ToUuid()); } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index 65199ca..f5feccc 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -1,9 +1,11 @@ +using System; + using FrostFS.Refs; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; + using Google.Protobuf; + using Microsoft.Extensions.Caching.Memory; -using System; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -13,16 +15,19 @@ public static class ContainerIdMapper .SetSlidingExpiration(TimeSpan.FromHours(1)) .SetSize(1); - public static ContainerID ToMessage(this ContainerId model) + public static ContainerID ToMessage(this FrostFsContainerId model) { - if (!Cache.Containers.TryGetValue(model, out ContainerID? message)) + if (model.Value == null) + throw new ArgumentNullException(nameof(model)); + + if (!Cache.Containers.TryGetValue(model.Value, out ContainerID? message)) { message = new ContainerID { Value = ByteString.CopyFrom(Base58.Decode(model.Value)) }; - Cache.Containers.Set(model, message, _oneHourExpiration); + Cache.Containers.Set(model.Value, message, _oneHourExpiration); } return message!; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs index 360ddd4..6b4901c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs @@ -1,4 +1,3 @@ -using FrostFS.SDK.ModelsV2; using FrostFS.Session; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs index 7d44809..31a8a0f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs @@ -1,14 +1,14 @@ using System.Linq; -using FrostFS.Netmap; -using FrostFS.SDK.ModelsV2.Netmap; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.Netmap; + +namespace FrostFS.SDK.ClientV2; public static class NetmapMapper { - public static NetmapSnapshot ToModel(this NetmapSnapshotResponse netmap) + public static FrostFsNetmapSnapshot ToModel(this NetmapSnapshotResponse netmap) { - return new NetmapSnapshot( + return new FrostFsNetmapSnapshot( netmap.Body.Netmap.Epoch, netmap.Body.Netmap.Nodes .Select(n => n.ToModel(netmap.MetaHeader.Version)) diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs index da87dfd..86e5ff0 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs @@ -1,30 +1,30 @@ using System; using System.Linq; -using FrostFS.Netmap; -using FrostFS.SDK.ModelsV2.Enums; -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK.ClientV2; public static class NodeInfoMapper { - public static NodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node) + public static FrostFsNodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node) { return node.NodeInfo.ToModel(node.Version); } - public static NodeInfo ToModel(this FrostFS.Netmap.NodeInfo nodeInfo, Refs.Version version) + public static FrostFsNodeInfo ToModel(this NodeInfo nodeInfo, Refs.Version version) { NodeState state = nodeInfo.State switch { - FrostFS.Netmap.NodeInfo.Types.State.Unspecified => NodeState.Unspecified, - FrostFS.Netmap.NodeInfo.Types.State.Online => NodeState.Online, - FrostFS.Netmap.NodeInfo.Types.State.Offline => NodeState.Offline, - FrostFS.Netmap.NodeInfo.Types.State.Maintenance => NodeState.Maintenance, + NodeInfo.Types.State.Unspecified => NodeState.Unspecified, + NodeInfo.Types.State.Online => NodeState.Online, + NodeInfo.Types.State.Offline => NodeState.Offline, + NodeInfo.Types.State.Maintenance => NodeState.Maintenance, _ => throw new ArgumentException($"Unknown NodeState. Value: '{nodeInfo.State}'.") }; - return new NodeInfo( + return new FrostFsNodeInfo( version: version.ToModel(), state: state, addresses: [.. nodeInfo.Addresses], diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs index 47b51fa..2f66ed8 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs @@ -1,11 +1,12 @@ using System.Linq; + using FrostFS.Netmap; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +namespace FrostFS.SDK.ClientV2; public static class PlacementPolicyMapper { - public static PlacementPolicy ToMessage(this ModelsV2.Netmap.PlacementPolicy placementPolicy) + public static PlacementPolicy ToMessage(this FrostFsPlacementPolicy placementPolicy) { var pp = new PlacementPolicy { @@ -23,9 +24,9 @@ public static class PlacementPolicyMapper return pp; } - public static ModelsV2.Netmap.PlacementPolicy ToModel(this PlacementPolicy placementPolicy) + public static FrostFsPlacementPolicy ToModel(this PlacementPolicy placementPolicy) { - return new ModelsV2.Netmap.PlacementPolicy( + return new FrostFsPlacementPolicy( placementPolicy.Unique, placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() ); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs index 215a127..422891d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs @@ -1,10 +1,10 @@ using FrostFS.Netmap; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +namespace FrostFS.SDK.ClientV2; public static class ReplicaMapper { - public static Replica ToMessage(this ModelsV2.Netmap.Replica replica) + public static Replica ToMessage(this FrostFsReplica replica) { return new Replica { @@ -13,8 +13,8 @@ public static class ReplicaMapper }; } - public static ModelsV2.Netmap.Replica ToModel(this Replica replica) + public static FrostFsReplica ToModel(this Replica replica) { - return new ModelsV2.Netmap.Replica((int)replica.Count, replica.Selector); + return new FrostFsReplica((int)replica.Count, replica.Selector); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs index bda31fe..0e10ffe 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -1,5 +1,3 @@ -using FrostFS.SDK.ModelsV2; - namespace FrostFS.SDK.ClientV2.Mappers.GRPC; internal static class ObjectMapper @@ -8,7 +6,7 @@ internal static class ObjectMapper { return new FrostFsObject(obj.Header.ToModel()) { - ObjectId = ObjectId.FromHash(obj.ObjectId.Value.ToByteArray()) + ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray()) }; } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs index 7528152..de596ad 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs @@ -1,11 +1,10 @@ using FrostFS.Object; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectAttributeMapper { - public static Header.Types.Attribute ToMessage(this ObjectAttribute attribute) + public static Header.Types.Attribute ToMessage(this FrostFsAttribute attribute) { return new Header.Types.Attribute { @@ -14,8 +13,8 @@ public static class ObjectAttributeMapper }; } - public static ObjectAttribute ToModel(this Header.Types.Attribute attribute) + public static FrostFsAttribute ToModel(this Header.Types.Attribute attribute) { - return new ObjectAttribute(attribute.Key, attribute.Value); + return new FrostFsAttribute(attribute.Key, attribute.Value); } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs index 8353f5f..e59f51b 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -1,7 +1,6 @@ using System; + using FrostFS.Object; -using FrostFS.SDK.ModelsV2; -using MatchType = FrostFS.Object.MatchType; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -11,11 +10,11 @@ public static class ObjectFilterMapper { var objMatchTypeName = filter.MatchType switch { - ModelsV2.Enums.ObjectMatchType.Unspecified => MatchType.Unspecified, - ModelsV2.Enums.ObjectMatchType.Equals => MatchType.StringEqual, - ModelsV2.Enums.ObjectMatchType.NotEquals => MatchType.StringNotEqual, - ModelsV2.Enums.ObjectMatchType.KeyAbsent => MatchType.NotPresent, - ModelsV2.Enums.ObjectMatchType.StartsWith => MatchType.CommonPrefix, + FrostFsObjectMatchType.Unspecified => MatchType.Unspecified, + FrostFsObjectMatchType.Equals => MatchType.StringEqual, + FrostFsObjectMatchType.NotEquals => MatchType.StringNotEqual, + FrostFsObjectMatchType.KeyAbsent => MatchType.NotPresent, + FrostFsObjectMatchType.StartsWith => MatchType.CommonPrefix, _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") }; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index 282e42e..fb4c77d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -1,75 +1,28 @@ using System; using System.Linq; + using FrostFS.Object; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using Google.Protobuf; -using ObjectType = FrostFS.Object.ObjectType; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectHeaderMapper { - public static Header ToMessage(this ObjectHeader header) + public static FrostFsObjectHeader ToModel(this Header header) { var objTypeName = header.ObjectType switch { - ModelsV2.Enums.ObjectType.Regular => ObjectType.Regular, - ModelsV2.Enums.ObjectType.Lock => ObjectType.Lock, - ModelsV2.Enums.ObjectType.Tombstone => ObjectType.Tombstone, + ObjectType.Regular => FrostFsObjectType.Regular, + ObjectType.Lock => FrostFsObjectType.Lock, + ObjectType.Tombstone => FrostFsObjectType.Tombstone, _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") }; - var head = new Header - { - OwnerId = header!.OwnerId != null ? header.OwnerId.ToMessage() : null, - Version = header!.Version != null ? header.Version.ToMessage() : null, - ContainerId = header.ContainerId.ToMessage(), - ObjectType = objTypeName, - PayloadLength = header.PayloadLength - }; - - foreach (var attribute in header.Attributes) - { - head.Attributes.Add(attribute.ToMessage()); - } - - var split = header.Split; - if (split != null) - { - head.Split = new Header.Types.Split - { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null - }; - } - - return head; - } - - public static ObjectHeader ToModel(this Header header) - { - var objTypeName = header.ObjectType switch - { - ObjectType.Regular => ModelsV2.Enums.ObjectType.Regular, - ObjectType.Lock => ModelsV2.Enums.ObjectType.Lock, - ObjectType.Tombstone => ModelsV2.Enums.ObjectType.Tombstone, - _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") - }; - - var model = new ObjectHeader( - new ContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), - objTypeName, - header.Attributes.Select(attribute => attribute.ToModel()).ToArray() - ) - { - PayloadLength = header.PayloadLength, - Version = header.Version.ToModel(), - OwnerId = header.OwnerId.ToModel() - }; + FrostFsSplit? split = null; if (header.Split != null) { - model.Split = new Split(new SplitId(header.Split.SplitId.ToUuid())) + split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid())) { Parent = header.Split.Parent?.ToModel(), ParentHeader = header.Split.ParentHeader?.ToModel(), @@ -77,9 +30,20 @@ public static class ObjectHeaderMapper }; if (header.Split.Children.Count != 0) - model.Split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); + split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); } + var model = new FrostFsObjectHeader( + new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), + objTypeName, + header.Attributes.Select(attribute => attribute.ToModel()).ToArray(), + split, + header.OwnerId.ToModel(), + header.Version.ToModel()) + { + PayloadLength = header.PayloadLength, + }; + return model; } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs index c9afe0c..d20a915 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs @@ -1,12 +1,12 @@ using FrostFS.Refs; -using FrostFS.SDK.ModelsV2; + using Google.Protobuf; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectIdMapper { - public static ObjectID ToMessage(this ObjectId objectId) + public static ObjectID ToMessage(this FrostFsObjectId objectId) { return new ObjectID { @@ -14,8 +14,8 @@ public static class ObjectIdMapper }; } - public static ObjectId ToModel(this ObjectID objectId) + public static FrostFsObjectId ToModel(this ObjectID objectId) { - return ObjectId.FromHash(objectId.Value.ToByteArray()); + return FrostFsObjectId.FromHash(objectId.Value.ToByteArray()); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs index de8db49..ff81321 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs @@ -1,9 +1,11 @@ +using System; + using FrostFS.Refs; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; + using Google.Protobuf; + using Microsoft.Extensions.Caching.Memory; -using System; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -13,7 +15,7 @@ public static class OwnerIdMapper .SetSlidingExpiration(TimeSpan.FromHours(1)) .SetSize(1); - public static OwnerID ToMessage(this OwnerId model) + public static OwnerID ToMessage(this FrostFsOwner model) { if (!Cache.Owners.TryGetValue(model, out OwnerID? message)) { @@ -28,11 +30,11 @@ public static class OwnerIdMapper return message!; } - public static OwnerId ToModel(this OwnerID message) + public static FrostFsOwner ToModel(this OwnerID message) { - if (!Cache.Owners.TryGetValue(message, out OwnerId? model)) + if (!Cache.Owners.TryGetValue(message, out FrostFsOwner? model)) { - model = new OwnerId(Base58.Encode(message.Value.ToByteArray())); + model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray())); Cache.Owners.Set(message, model, _oneHourExpiration); } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs index 4343d4d..0b280cf 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs @@ -1,12 +1,12 @@ using System; -using FrostFS.SDK.ModelsV2; + using Google.Protobuf; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class SignatureMapper { - public static Refs.Signature ToMessage(this Signature signature) + public static Refs.Signature ToMessage(this FrostFsSignature signature) { var scheme = signature.Scheme switch { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs index d489239..d3555ac 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs @@ -1,19 +1,18 @@ using System; -using FrostFS.SDK.ModelsV2.Enums; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class StatusMapper { - public static ModelsV2.ResponseStatus ToModel(this Status.Status status) + public static FrostFsResponseStatus ToModel(this Status.Status status) { if (status is null) - return new ModelsV2.ResponseStatus(StatusCode.Success); + return new FrostFsResponseStatus(FrostFsStatusCode.Success); + + var codeName = Enum.GetName(typeof(FrostFsStatusCode), status.Code); - var codeName = Enum.GetName(typeof(StatusCode), status.Code); - return codeName is null ? throw new ArgumentException($"Unknown StatusCode. Value: '{status.Code}'.") - : new ModelsV2.ResponseStatus((StatusCode)Enum.Parse(typeof(StatusCode), codeName), status.Message); + : new FrostFsResponseStatus((FrostFsStatusCode)status.Code, status.Message); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs index df2066c..f17509a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Threading; -using Version = FrostFS.Refs.Version; + +using FrostFS.Refs; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -10,7 +11,7 @@ public static class VersionMapper private static readonly Hashtable _cacheModels = []; private static SpinLock _spinlock = new(); - public static Version ToMessage(this ModelsV2.Version model) + public static Version ToMessage(this FrostFsVersion model) { var key = model.Major << 16 + model.Minor; @@ -31,7 +32,7 @@ public static class VersionMapper } catch (System.ArgumentException) { - // ignore attempt to add duplicate error. + // ignore attempt to add duplicate } finally { @@ -43,7 +44,7 @@ public static class VersionMapper return (Version)_cacheMessages[key]; } - public static ModelsV2.Version ToModel(this Version message) + public static FrostFsVersion ToModel(this Version message) { var key = (int)message.Major << 16 + (int)message.Minor; @@ -53,14 +54,14 @@ public static class VersionMapper try { _spinlock.Enter(ref lockTaken); - var model = new ModelsV2.Version((int)message.Major, (int)message.Minor); + var model = new FrostFsVersion((int)message.Major, (int)message.Minor); _cacheModels.Add(key, model); return model; } catch (System.ArgumentException) { - // ignore attempt to add duplicate error. + // ignore attempt to add duplicate } finally { @@ -69,6 +70,6 @@ public static class VersionMapper } } - return (ModelsV2.Version)_cacheModels[key]; + return (FrostFsVersion)_cacheModels[key]; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs new file mode 100644 index 0000000..650c7d6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FrostFS.SDK; + +public class ClientSettings +{ + protected static readonly string errorTemplate = "{0} is required parameter"; + + public string Host { get; set; } = string.Empty; + + public virtual void Validate() + { + var errors = CheckFields(); + if (errors != null) + ThrowException(errors); + } + + protected List? CheckFields() + { + List? errors = null; + + if (string.IsNullOrWhiteSpace(Host)) + (errors ??= []).Add(string.Format(errorTemplate, nameof(Host))); + + return errors; + } + + protected static void ThrowException(List errors) + { + StringBuilder messages = new(); + + foreach (var error in errors) + { + messages.AppendLine(error); + } + + throw new ArgumentException(messages.ToString()); + } +} + +public class SingleOwnerClientSettings : ClientSettings +{ + public string Key { get; set; } = string.Empty; + + public override void Validate() + { + var errors = CheckFields(); + if (errors != null) + ThrowException(errors); + } + + protected new List? CheckFields() + { + List? errors = base.CheckFields(); + + if (string.IsNullOrWhiteSpace(Key)) + (errors ??= []).Add(string.Format(errorTemplate, nameof(Key))); + + return errors; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs new file mode 100644 index 0000000..895f25a --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs @@ -0,0 +1,62 @@ +using FrostFS.Refs; +using FrostFS.SDK.Cryptography; + +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2; + +namespace FrostFS.SDK; + +public class FrostFsContainerId +{ + private string? modelId; + private ContainerID? containerID; + + public FrostFsContainerId(string id) + { + this.modelId = id; + } + + internal FrostFsContainerId(ContainerID id) + { + this.containerID = id; + } + + public string Value + { + get + { + if (this.modelId != null) + return this.modelId; + + if (containerID != null) + { + this.modelId = Base58.Encode(containerID.Value.ToByteArray()); + return this.modelId; + } + + throw new InvalidObjectException(); + } + } + + internal ContainerID ContainerID + { + get + { + if (this.containerID != null) + return this.containerID; + + if (modelId != null) + { + this.containerID = this.ToMessage(); + return this.containerID; + } + + throw new InvalidObjectException(); + } + } + + public override string ToString() + { + return Value; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs new file mode 100644 index 0000000..4a4d140 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +using Google.Protobuf; + +namespace FrostFS.SDK; + +public class FrostFsContainerInfo +{ + private Container.Container.Types.Attribute[]? grpsAttributes; + private List? attributes; + private FrostFsPlacementPolicy? placementPolicy; + private Guid? nonce; + + private Container.Container container; + + public FrostFsContainerInfo( + BasicAcl basicAcl, + FrostFsPlacementPolicy placementPolicy, + List? attributes = null, + FrostFsVersion? version = null, + FrostFsOwner? owner = null, + Guid? nonce = null) + { + BasicAcl = basicAcl; + this.placementPolicy = placementPolicy; + this.attributes = attributes; + Version = version; + Owner = owner; + this.nonce = nonce; + } + + internal FrostFsContainerInfo(Container.Container container) + { + this.container = container; + } + + public Guid Nonce + { + get + { + nonce ??= container?.Nonce != null ? container.Nonce.ToUuid() : Guid.NewGuid(); + return nonce.Value; + } + } + + public BasicAcl BasicAcl { get; private set; } + + public FrostFsPlacementPolicy? PlacementPolicy + { + get + { + placementPolicy ??= container.PlacementPolicy?.ToModel(); + return placementPolicy; + } + } + + public List? Attributes + { + get + { + if (attributes == null && grpsAttributes != null) + attributes = grpsAttributes.Select(a => new FrostFsAttribute(a.Key, a.Value)).ToList(); + + return attributes; + } + } + + public FrostFsVersion? Version { get; private set; } + + public FrostFsOwner? Owner { get; private set; } + + internal Container.Container.Types.Attribute[]? GetGrpsAttributes() + { + grpsAttributes ??= Attributes? + .Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value }) + .ToArray(); + + return grpsAttributes; + } + + internal Container.Container GetContainer() + { + if (this.container == null) + { + this.container = new Container.Container() + { + BasicAcl = (uint)BasicAcl, + PlacementPolicy = PlacementPolicy.ToMessage(), + Nonce = ByteString.CopyFrom(Nonce.ToBytes()), + OwnerId = Owner?.OwnerID, + Version = Version?.Version + }; + + var attribs = GetGrpsAttributes(); + if (attribs != null) + this.container.Attributes.AddRange(attribs); + } + + return this.container; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs new file mode 100644 index 0000000..dd3252b --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs @@ -0,0 +1,21 @@ +using System.ComponentModel; + +namespace FrostFS.SDK; + +public enum BasicAcl +{ + [Description("Not defined ACL")] + NotDefined = 0x00000000, + + [Description("Basic ACL for private container")] + Private = 0x1C8C8CCC, + + [Description("Basic ACL for public RO container")] + PublicRO = 0x1FBF8CFF, + + [Description("Basic ACL for public RW container")] + PublicRW = 0x1FBFBFFF, + + [Description("Basic ACL for public append container")] + PublicAppend = 0x1FBF9FFF, +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/ObjectMatchType.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs similarity index 59% rename from src/FrostFS.SDK.ModelsV2/Enums/ObjectMatchType.cs rename to src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs index 4d6026c..1fdeb60 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/ObjectMatchType.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs @@ -1,6 +1,6 @@ -namespace FrostFS.SDK.ModelsV2.Enums; +namespace FrostFS.SDK; -public enum ObjectMatchType +public enum FrostFsObjectMatchType { Unspecified = 0, Equals = 1, diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs new file mode 100644 index 0000000..793fe2e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK; + +public enum FrostFsObjectType +{ + Regular = 0, + Tombstone = 1, + Lock = 3 +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/StatusCode.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs similarity index 88% rename from src/FrostFS.SDK.ModelsV2/Enums/StatusCode.cs rename to src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs index df0fa57..7eb185c 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/StatusCode.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs @@ -1,6 +1,6 @@ -namespace FrostFS.SDK.ModelsV2.Enums; +namespace FrostFS.SDK; -public enum StatusCode +public enum FrostFsStatusCode { Success = 0, Internal = 1024, diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs new file mode 100644 index 0000000..2821e55 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs @@ -0,0 +1,9 @@ +namespace FrostFS.SDK; + +public enum NodeState +{ + Unspecified = 0, + Online = 1, + Offline = 2, + Maintenance = 3 +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs new file mode 100644 index 0000000..4b5634c --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK; + +public enum SignatureScheme +{ + EcdsaSha512, + EcdsaRfc6979Sha256, + EcdsaRfc6979Sha256WalletConnect +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs b/src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs new file mode 100644 index 0000000..706b3f2 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK; + +public class CallStatistics +{ + public string? MethodName { get; set; } + public long ElapsedMicroSeconds { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs b/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs new file mode 100644 index 0000000..82017f9 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs @@ -0,0 +1,20 @@ +using FrostFS.SDK.Cryptography; +using System; + +namespace FrostFS.SDK; + +public class CheckSum +{ + // type is always Sha256 + public byte[]? Hash { get; set; } + + public static CheckSum CreateCheckSum(byte[] content) + { + return new CheckSum { Hash = content.Sha256() }; + } + + public override string ToString() + { + return BitConverter.ToString(Hash).Replace("-", ""); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs b/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs new file mode 100644 index 0000000..332a3e3 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs @@ -0,0 +1,52 @@ +namespace FrostFS.SDK; + +public class Constants +{ + public const int ObjectChunkSize = 3 * (1 << 20); + public const int Sha256HashLength = 32; + + // HeaderPrefix is a prefix of key to object header value or property. + public const string HeaderPrefix = "$Object:"; + + // FilterHeaderVersion is a filter key to "version" field of the object header. + public const string FilterHeaderVersion = HeaderPrefix + "version"; + + // FilterHeaderObjectID is a filter key to "object_id" field of the object. + public const string FilterHeaderObjectID = HeaderPrefix + "objectID"; + + // FilterHeaderContainerID is a filter key to "container_id" field of the object header. + public const string FilterHeaderContainerID = HeaderPrefix + "containerID"; + + // FilterHeaderOwnerID is a filter key to "owner_id" field of the object header. + public const string FilterHeaderOwnerID = HeaderPrefix + "ownerID"; + + // FilterHeaderCreationEpoch is a filter key to "creation_epoch" field of the object header. + public const string FilterHeaderCreationEpoch = HeaderPrefix + "creationEpoch"; + + // FilterHeaderPayloadLength is a filter key to "payload_length" field of the object header. + public const string FilterHeaderPayloadLength = HeaderPrefix + "payloadLength"; + + // FilterHeaderPayloadHash is a filter key to "payload_hash" field of the object header. + public const string FilterHeaderPayloadHash = HeaderPrefix + "payloadHash"; + + // FilterHeaderObjectType is a filter key to "object_type" field of the object header. + public const string FilterHeaderObjectType = HeaderPrefix + "objectType"; + + // FilterHeaderHomomorphicHash is a filter key to "homomorphic_hash" field of the object header. + public const string FilterHeaderHomomorphicHash = HeaderPrefix + "homomorphicHash"; + + // FilterHeaderParent is a filter key to "split.parent" field of the object header. + public const string FilterHeaderParent = HeaderPrefix + "split.parent"; + + // FilterHeaderSplitID is a filter key to "split.splitID" field of the object header. + public const string FilterHeaderSplitID = HeaderPrefix + "split.splitID"; + + // FilterHeaderECParent is a filter key to "ec.parent" field of the object header. + public const string FilterHeaderECParent = HeaderPrefix + "ec.parent"; + + // FilterPropertyRoot is a filter key to check if regular object is on top of split hierarchy. + public const string FilterHeaderRoot = HeaderPrefix + "ROOT"; + + // FilterPropertyPhy is a filter key to check if an object physically stored on a node. + public const string FilterHeaderPhy = HeaderPrefix + "PHY"; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs new file mode 100644 index 0000000..3033ec6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK; + +public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) +{ + public ulong Epoch { get; private set; } = epoch; + + public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs similarity index 74% rename from src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs rename to src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs index 8fa4704..9581b21 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs @@ -1,18 +1,17 @@ using System; using System.Collections.Generic; -using FrostFS.SDK.ModelsV2.Enums; -namespace FrostFS.SDK.ModelsV2.Netmap; +namespace FrostFS.SDK; -public class NodeInfo( - Version version, +public class FrostFsNodeInfo( + FrostFsVersion version, NodeState state, IReadOnlyCollection addresses, IReadOnlyDictionary attributes, ReadOnlyMemory publicKey) { public NodeState State { get; private set; } = state; - public Version Version { get; private set; } = version; + public FrostFsVersion Version { get; private set; } = version; public IReadOnlyCollection Addresses { get; private set; } = addresses; public IReadOnlyDictionary Attributes { get; private set; } = attributes; public ReadOnlyMemory PublicKey { get; private set; } = publicKey; diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs similarity index 59% rename from src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs rename to src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs index bb7b1a3..410598c 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs @@ -1,16 +1,16 @@ using System; using System.Linq; -namespace FrostFS.SDK.ModelsV2.Netmap; +namespace FrostFS.SDK; -public class PlacementPolicy(bool unique, params Replica[] replicas) : IComparable +public class FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) : IComparable { - public Replica[] Replicas { get; private set; } = replicas; + public FrostFsReplica[] Replicas { get; private set; } = replicas; public bool Unique { get; private set; } = unique; - public int CompareTo(PlacementPolicy other) + public int CompareTo(FrostFsPlacementPolicy other) { - var notEqual = other == null + var notEqual = other == null || Unique != other.Unique || Replicas.Length != other.Replicas.Length; diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs similarity index 60% rename from src/FrostFS.SDK.ModelsV2/Netmap/Replica.cs rename to src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs index a6aeac9..8fd1bd9 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/Replica.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs @@ -1,14 +1,14 @@ -namespace FrostFS.SDK.ModelsV2.Netmap; +namespace FrostFS.SDK; -public class Replica +public class FrostFsReplica { public int Count { get; set; } public string Selector { get; set; } - public Replica(int count, string? selector = null) + public FrostFsReplica(int count, string? selector = null) { selector ??= string.Empty; - + Count = count; Selector = selector; } diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs new file mode 100644 index 0000000..da66403 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs @@ -0,0 +1,31 @@ +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK; + +public class FrostFsVersion(int major, int minor) +{ + public Version version; + + public int Major { get; set; } = major; + public int Minor { get; set; } = minor; + + internal Version Version + { + get + { + this.version ??= this.ToMessage(); + return this.version; + } + } + + public bool IsSupported(FrostFsVersion version) + { + return Major == version.Major; + } + + public override string ToString() + { + return $"v{Major}.{Minor}"; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs new file mode 100644 index 0000000..8a69fd4 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK; + +public class FrostFsAttribute(string key, string value) +{ + public string Key { get; set; } = key; + + public string Value { get; set; } = value; +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs new file mode 100644 index 0000000..12a95c6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs @@ -0,0 +1,9 @@ +namespace FrostFS.SDK; + +public class FrostFsLargeObject(FrostFsContainerId container) : FrostFsObject(container) +{ + public ulong PayloadLength + { + get { return Header!.PayloadLength; } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs new file mode 100644 index 0000000..5b9e349 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs @@ -0,0 +1,13 @@ +namespace FrostFS.SDK; + +public class FrostFsLinkObject : FrostFsObject +{ + public FrostFsLinkObject(FrostFsContainerId containerId, SplitId splitId, FrostFsObjectHeader largeObjectHeader) + : base(containerId) + { + Header!.Split = new FrostFsSplit(splitId) + { + ParentHeader = largeObjectHeader + }; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs new file mode 100644 index 0000000..5ee8b8e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs @@ -0,0 +1,64 @@ +using System; + +namespace FrostFS.SDK; + +public class FrostFsObject +{ + /// + /// Creates new instance from ObjectHeader + /// + /// + public FrostFsObject(FrostFsObjectHeader header) + { + Header = header; + } + + /// + /// Creates new instance with specified parameters + /// + /// + /// + public FrostFsObject(FrostFsContainerId container, FrostFsObjectType objectType = FrostFsObjectType.Regular) + { + Header = new FrostFsObjectHeader(containerId: container, type: objectType); + } + + /// + /// Header contains metadata for the object + /// + /// + public FrostFsObjectHeader Header { get; set; } + + /// + /// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed. + /// + public FrostFsObjectId? ObjectId + { + get; set; + } + + /// + /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings + /// Used only for PutSingleObject method + /// + /// Buffer for output data + public byte[] Payload { get; set; } = []; + + /// + /// A payload is obtained via stream reader + /// + /// Reader for received data + public IObjectReader? ObjectReader { get; set; } + + /// + /// Applied only for the last Object in chain in case of manual multipart uploading + /// + /// Parent for multipart object + public void SetParent(FrostFsObjectHeader largeObjectHeader) + { + if (Header?.Split == null) + throw new Exception("The object is not initialized properly"); + + Header.Split.ParentHeader = largeObjectHeader; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs new file mode 100644 index 0000000..e9552bf --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs @@ -0,0 +1,111 @@ +namespace FrostFS.SDK; + +public interface IObjectFilter +{ + public FrostFsObjectMatchType MatchType { get; set; } + public string Key { get; set; } + + string? GetSerializedValue(); +} + +public abstract class FrostFsObjectFilter(FrostFsObjectMatchType matchType, string key, T value) : IObjectFilter +{ + public FrostFsObjectMatchType MatchType { get; set; } = matchType; + public string Key { get; set; } = key; + + public T Value { get; set; } = value; + + public string? GetSerializedValue() + { + return Value?.ToString(); + } +} + +/// +/// Creates filter to search by Attribute +/// +/// Match type +/// Attribute key +/// Attribute value +public class FilterByAttribute(FrostFsObjectMatchType matchType, string key, string value) : FrostFsObjectFilter(matchType, key, value) { } + +/// +/// Creates filter to search by ObjectId +/// +/// Match type +/// ObjectId +public class FilterByObjectId(FrostFsObjectMatchType matchType, FrostFsObjectId objectId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } + +/// +/// Creates filter to search by OwnerId +/// +/// Match type +/// ObjectId +public class FilterByOwnerId(FrostFsObjectMatchType matchType, FrostFsOwner ownerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) { } + +/// +/// Creates filter to search by Version +/// +/// Match type +/// Version +public class FilterByVersion(FrostFsObjectMatchType matchType, FrostFsVersion version) : FrostFsObjectFilter(matchType, Constants.FilterHeaderVersion, version) { } + +/// +/// Creates filter to search by ContainerId +/// +/// Match type +/// ContainerId +public class FilterByContainerId(FrostFsObjectMatchType matchType, FrostFsContainerId containerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) { } + +/// +/// Creates filter to search by creation Epoch +/// +/// Match type +/// Creation Epoch +public class FilterByEpoch(FrostFsObjectMatchType matchType, ulong epoch) : FrostFsObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) { } + +/// +/// Creates filter to search by Payload Length +/// +/// Match type +/// Payload Length +public class FilterByPayloadLength(FrostFsObjectMatchType matchType, ulong payloadLength) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { } + +/// +/// Creates filter to search by Payload Hash +/// +/// Match type +/// Payload Hash +public class FilterByPayloadHash(FrostFsObjectMatchType matchType, CheckSum payloadHash) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { } + +/// +/// Creates filter to search by Parent +/// +/// Match type +/// Parent +public class FilterByParent(FrostFsObjectMatchType matchType, FrostFsObjectId parentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderParent, parentId) { } + +/// +/// Creates filter to search by SplitId +/// +/// Match type +/// SplitId +public class FilterBySplitId(FrostFsObjectMatchType matchType, SplitId splitId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) { } + +/// +/// Creates filter to search by Payload Hash +/// +/// Match type +/// Payload Hash +public class FilterByECParent(FrostFsObjectMatchType matchType, FrostFsObjectId ecParentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) { } + +/// +/// Creates filter to search Root objects +/// +public class FilterByRootObject() : FrostFsObjectFilter(FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { } + +/// +/// Creates filter to search objects that are physically stored on the server +/// (FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { } + diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs new file mode 100644 index 0000000..e384ece --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using FrostFS.Object; + +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK; + +public class FrostFsObjectHeader( + FrostFsContainerId containerId, + FrostFsObjectType type = FrostFsObjectType.Regular, + FrostFsAttribute[]? attributes = null, + FrostFsSplit? split = null, + FrostFsOwner? owner = null, + FrostFsVersion? version = null) +{ + private Header header; + private Container.Container.Types.Attribute[]? grpsAttributes; + + public List Attributes { get; internal set; } = attributes != null ? [.. attributes] : []; + + public FrostFsContainerId ContainerId { get; } = containerId; + + public ulong PayloadLength { get; set; } + + public byte[]? PayloadCheckSum { get; set; } + + public FrostFsObjectType ObjectType { get; } = type; + + public FrostFsOwner? OwnerId { get; internal set; } = owner; + + public FrostFsVersion? Version { get; internal set; } = version; + + public FrostFsSplit? Split { get; internal set; } = split; + + internal Container.Container.Types.Attribute[]? GetGrpsAttributes() + { + grpsAttributes ??= Attributes? + .Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value }) + .ToArray(); + + return grpsAttributes; + } + + public Header GetHeader() + { + if (header == null) + { + var objTypeName = ObjectType switch + { + FrostFsObjectType.Regular => Object.ObjectType.Regular, + FrostFsObjectType.Lock => Object.ObjectType.Lock, + FrostFsObjectType.Tombstone => Object.ObjectType.Tombstone, + _ => throw new ArgumentException($"Unknown ObjectType. Value: '{ObjectType}'.") + }; + + this.header = new Header + { + OwnerId = OwnerId?.ToMessage(), + Version = Version?.ToMessage(), + ContainerId = ContainerId.ToMessage(), + ObjectType = objTypeName, + PayloadLength = PayloadLength + }; + + foreach (var attribute in Attributes) + { + this.header.Attributes.Add(attribute.ToMessage()); + } + + var split = Split; + if (split != null) + { + this.header.Split = new Header.Types.Split + { + SplitId = split!.SplitId != null ? split.SplitId.GetSplitId() : null + }; + } + } + + return this.header; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs new file mode 100644 index 0000000..4e4dbf0 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs @@ -0,0 +1,28 @@ +using System; + +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK; + +public class FrostFsObjectId(string id) +{ + public string Value { get; } = id; + + public static FrostFsObjectId FromHash(byte[] hash) + { + if (hash.Length != Constants.Sha256HashLength) + throw new FormatException("ObjectID must be a sha256 hash."); + + return new FrostFsObjectId(Base58.Encode(hash)); + } + + public byte[] ToHash() + { + return Base58.Decode(Value); + } + + public override string ToString() + { + return Value; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs new file mode 100644 index 0000000..d249d80 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs @@ -0,0 +1,38 @@ +using System.Security.Cryptography; + +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK; + +public class FrostFsOwner(string id) +{ + private OwnerID ownerID; + + public string Value { get; } = id; + + public static FrostFsOwner FromKey(ECDsa key) + { + return new FrostFsOwner(key.PublicKey().PublicKeyToAddress()); + } + + internal OwnerID OwnerID + { + get + { + ownerID ??= this.ToMessage(); + return ownerID; + } + } + + public byte[] ToHash() + { + return Base58.Decode(Value); + } + + public override string ToString() + { + return Value; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs new file mode 100644 index 0000000..947662f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK; + +public class FrostFsSplit(SplitId splitId) +{ + public FrostFsSplit() : this(new SplitId()) + { + } + + public SplitId SplitId { get; private set; } = splitId; + + public FrostFsObjectId? Parent { get; set; } + + public FrostFsObjectId? Previous { get; set; } + + public FrostFsSignature? ParentSignature { get; set; } + + public FrostFsObjectHeader? ParentHeader { get; set; } + + public List Children { get; } = []; + + public Refs.Signature ParentSignatureGrpc { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs b/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs new file mode 100644 index 0000000..51211fd --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FrostFS.SDK; + +public interface IObjectReader : IDisposable +{ + Task?> ReadChunk(CancellationToken cancellationToken = default); +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs b/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs new file mode 100644 index 0000000..bb8e617 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs @@ -0,0 +1,62 @@ +using FrostFS.SDK.Cryptography; + +using Google.Protobuf; + +using System; + +namespace FrostFS.SDK; + +public class SplitId +{ + private readonly Guid id; + + private ByteString? _message; + + public SplitId() + { + id = Guid.NewGuid(); + } + + public SplitId(Guid guid) + { + id = guid; + } + + private SplitId(byte[] binary) + { + id = new Guid(binary); + } + + private SplitId(string str) + { + id = new Guid(str); + } + + public static SplitId CreateFromBinary(byte[] binaryData) + { + return new SplitId(binaryData); + } + + public static SplitId CreateFromString(string stringData) + { + return new SplitId(stringData); + } + + public override string ToString() + { + return id.ToString(); + } + + public byte[]? ToBinary() + { + if (id == Guid.Empty) + return null; + + return id.ToBytes(); + } + + public ByteString? GetSplitId() + { + return _message ??= ByteString.CopyFrom(ToBinary()); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs new file mode 100644 index 0000000..b5e5831 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs @@ -0,0 +1,14 @@ +namespace FrostFS.SDK; + +public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null) +{ + public FrostFsStatusCode Code { get; set; } = code; + public string Message { get; set; } = message ?? string.Empty; + + public bool IsSuccess => Code == FrostFsStatusCode.Success; + + public override string ToString() + { + return $"Response status: {Code}. Message: {Message}."; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs new file mode 100644 index 0000000..d7ca6b6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK; + +public class FrostFsSignature +{ + public byte[]? Key { get; set; } + + public byte[]? Sign { get; set; } + + public SignatureScheme Scheme { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs new file mode 100644 index 0000000..36dad09 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs @@ -0,0 +1,20 @@ +namespace FrostFS.SDK; + +public class MetaHeader(FrostFsVersion version, int epoch, int ttl) +{ + public FrostFsVersion Version { get; set; } = version; + public int Epoch { get; set; } = epoch; + public int Ttl { get; set; } = ttl; + + public static MetaHeader Default() + { + return new MetaHeader( + new FrostFsVersion( + major: 2, + minor: 13 + ), + epoch: 0, + ttl: 2 + ); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs new file mode 100644 index 0000000..8954780 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK; + +public class FrostFsSessionToken(byte[] token) +{ + public byte[] Token { get; private set; } = token; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs index ca3728a..f1704a9 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs @@ -2,9 +2,11 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Threading; -using FrostFS.SDK.ModelsV2; + using FrostFS.SDK.Cryptography; + using Google.Protobuf; + using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; @@ -13,13 +15,13 @@ public class Context() { private List? interceptors; - private ByteString publicKeyCache; + private ByteString? publicKeyCache; public ECDsa Key { get; set; } - public OwnerId OwnerId { get; set; } + public FrostFsOwner OwnerId { get; set; } - public ModelsV2.Version Version { get; set; } + public FrostFsVersion Version { get; set; } public CancellationToken CancellationToken { get; set; } = default; @@ -35,16 +37,13 @@ public class Context() set { this.interceptors = value; } } - public ByteString PublicKeyCache + public ByteString? GetPublicKeyCache() { - get + if (publicKeyCache == null && Key != null) { - if (publicKeyCache == null) - { - publicKeyCache = ByteString.CopyFrom(Key.PublicKey()); - } - - return publicKeyCache; + publicKeyCache = ByteString.CopyFrom(Key.PublicKey()); } + + return publicKeyCache; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs b/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs index a4915c3..ebc7558 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs @@ -1,11 +1,10 @@ -using FrostFS.SDK.ModelsV2; -using System.Security.Cryptography; +using System.Security.Cryptography; -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; -public class Credentials(ECDsa key, OwnerId ownerId) +public class Credentials(ECDsa key, FrostFsOwner ownerId) { public ECDsa Key { get; } = key; - public OwnerId OwnerId { get; } = ownerId; + public FrostFsOwner OwnerId { get; } = ownerId; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs index c48dc8a..bd333f7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public interface IContext { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs b/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs index c5ac1da..cc3ed0f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs @@ -1,6 +1,4 @@ -using FrostFS.SDK.ModelsV2; - -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public interface ISessionToken { @@ -10,5 +8,5 @@ public interface ISessionToken /// member. The session has a limited validity period, and applies to a strictly defined set of operations. /// /// Instance of the session obtained from the server - SessionToken? SessionToken { get; set; } + FrostFsSessionToken? SessionToken { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs index 09ca363..1aedefe 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs @@ -1,6 +1,6 @@ using System.Collections.Specialized; -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public class PrmBase() : IContext { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index a2a3315..01f87a8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -1,11 +1,8 @@ -using FrostFS.SDK.ModelsV2; -using System.Security.Cryptography; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmContainerCreate(ModelsV2.Container container) : PrmBase, ISessionToken +public sealed class PrmContainerCreate(FrostFsContainerInfo container) : PrmBase, ISessionToken { - public ModelsV2.Container Container { get; set; } = container; + public FrostFsContainerInfo Container { get; set; } = container; /// /// Since the container becomes available with some delay, it needs to poll the container status @@ -16,5 +13,5 @@ public sealed class PrmContainerCreate(ModelsV2.Container container) : PrmBase, /// /// Blank session token /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs index 63308aa..c97e80f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs @@ -1,10 +1,8 @@ -using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmContainerDelete(ContainerId containerId) : PrmBase, ISessionToken +public sealed class PrmContainerDelete(FrostFsContainerId containerId) : PrmBase, ISessionToken { - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; set; } = containerId; /// /// Since the container is removed with some delay, it needs to poll the container status @@ -12,5 +10,5 @@ public sealed class PrmContainerDelete(ContainerId containerId) : PrmBase, ISess /// Rules for polling the result public PrmWait? WaitParams { get; set; } - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs index cebdaca..f0c734b 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs @@ -1,8 +1,6 @@ -using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmContainerGet(ContainerId containerId) : PrmBase +public sealed class PrmContainerGet(FrostFsContainerId container) : PrmBase { - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId Container { get; set; } = container; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index b365188..b7d3980 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmContainerGetAll() : PrmBase() { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index e8387dc..f129109 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmNetmapSnapshot() : PrmBase { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs index c82db6e..3e82ff5 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmNetworkSettings() : PrmBase { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs index 2b3b661..7a3d1dc 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmNodeInfo() : PrmBase { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs index 3195bb8..f1dad65 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs @@ -1,13 +1,11 @@ -using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmObjectDelete(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken { - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; set; } = containerId; - public ObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; set; } = objectId; /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs index 07e717f..81a1e2f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs @@ -1,13 +1,11 @@ -using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmObjectGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken { - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; set; } = containerId; - public ObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; set; } = objectId; /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index f5a9b77..67919aa 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -1,13 +1,11 @@ -using FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK.ClientV2; -namespace FrostFS.SDK.ClientV2.Parameters; - -public sealed class PrmObjectHeadGet(ContainerId containerId, ObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken { - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; set; } = containerId; - public ObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; set; } = objectId; /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index e2c7a51..0be09e8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -1,7 +1,6 @@ using System.IO; -using FrostFS.SDK.ModelsV2; -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmObjectPut : PrmBase, ISessionToken { @@ -10,7 +9,7 @@ public sealed class PrmObjectPut : PrmBase, ISessionToken /// Optional parameters ike Attributes can be provided as well. /// /// Header with required parameters to create an object - public ObjectHeader? Header { get; set; } + public FrostFsObjectHeader? Header { get; set; } /// /// A stream with source data @@ -37,7 +36,7 @@ public sealed class PrmObjectPut : PrmBase, ISessionToken public byte[]? CustomBuffer { get; set; } /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } internal int MaxObjectSizeCache { get; set; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index 778b4a0..a9310dd 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -1,14 +1,14 @@ -using FrostFS.SDK.ModelsV2; -using System.Collections.Generic; -namespace FrostFS.SDK.ClientV2.Parameters; +using System.Collections.Generic; -public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmObjectSearch(FrostFsContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken { /// /// Defines container for the search /// /// - public ContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; set; } = containerId; /// /// Defines the search criteria @@ -17,5 +17,5 @@ public sealed class PrmObjectSearch(ContainerId containerId, params IObjectFilte public IEnumerable Filters { get; set; } = filters; /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs index 2afc76f..d7bcdb6 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmSessionCreate(ulong expiration) : PrmBase { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs index fc22bf7..3a8fa5d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs @@ -1,11 +1,9 @@ -using FrostFS.SDK.ModelsV2; - -namespace FrostFS.SDK.ClientV2.Parameters; +namespace FrostFS.SDK.ClientV2; public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken { public FrostFsObject FrostFsObject { get; set; } = frostFsObject; /// - public SessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs index 86e32d7..d914719 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs @@ -1,5 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2.Parameters; + +namespace FrostFS.SDK.ClientV2; public class PrmWait(TimeSpan timeout, TimeSpan pollInterval) { diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 5811a3b..d959a5c 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Threading.Tasks; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2; using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.ClientV2; using FrostFS.Refs; using FrostFS.Session; @@ -18,14 +17,14 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { readonly SessionProvider sessions = new(context); - public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) { return await sessions.GetOrCreateSession(args, ctx); } - internal async Task GetContainerAsync(PrmContainerGet args) + internal async Task GetContainerAsync(PrmContainerGet args) { - GetRequest request = GetContainerRequest(args.ContainerId.ToMessage(), args.XHeaders, args.Context!); + GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context!); var response = await service.GetAsync(request, null, args.Context!.Deadline, args.Context.CancellationToken); @@ -34,15 +33,15 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient return response.Body.Container.ToModel(); } - internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) + internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; var request = new ListRequest { - Body = new ListRequest.Types.Body + Body = new () { - OwnerId = ctx.OwnerId.ToMessage() + OwnerId = ctx.OwnerId.ToMessage() } }; @@ -55,16 +54,18 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient foreach (var cid in response.Body.ContainerIds) { - yield return new ContainerId(Base58.Encode(cid.Value.ToByteArray())); + yield return new FrostFsContainerId(Base58.Encode(cid.Value.ToByteArray())); } } - internal async Task CreateContainerAsync(PrmContainerCreate args) + internal async Task CreateContainerAsync(PrmContainerCreate args) { var ctx = args.Context!; - var grpcContainer = args.Container.ToMessage(); - grpcContainer.OwnerId = ctx.OwnerId.ToMessage(); - grpcContainer.Version = ctx.Version.ToMessage(); + + var grpcContainer = args.Container.GetContainer(); + + grpcContainer.OwnerId ??= ctx.OwnerId.ToMessage(); + grpcContainer.Version ??= ctx.Version.ToMessage(); var request = new PutRequest { @@ -81,9 +82,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient null, ContainerSessionContext.Types.Verb.Put, ctx.Key, - ctx.PublicKeyCache); - - var v = sessionToken.Body.OwnerId == grpcContainer.OwnerId; + ctx.GetPublicKeyCache()); request.AddMetaHeader(args.XHeaders, sessionToken); @@ -95,7 +94,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx); - return new ContainerId(Base58.Encode(response.Body.ContainerId.Value.ToByteArray())); + return new FrostFsContainerId(response.Body.ContainerId); } internal async Task DeleteContainerAsync(PrmContainerDelete args) @@ -116,7 +115,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient request.Body.ContainerId, ContainerSessionContext.Types.Verb.Delete, ctx.Key, - ctx.PublicKeyCache); + ctx.GetPublicKeyCache()); request.AddMetaHeader(args.XHeaders, sessionToken); @@ -193,7 +192,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); - if (ex.Status.Code != ModelsV2.Enums.StatusCode.ContainerNotFound) + if (ex.Status.Code != FrostFsStatusCode.ContainerNotFound) throw; if (expect == WaitExpects.Removed) diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 60c7e1f..98523ac 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -1,13 +1,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using FrostFS.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using NodeInfo = FrostFS.SDK.ModelsV2.Netmap.NodeInfo; -using FrostFS.SDK.ModelsV2.Netmap; +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2; + using static FrostFS.Netmap.NetworkConfig.Types; -using FrostFS.SDK.ClientV2.Parameters; namespace FrostFS.SDK.ClientV2; @@ -40,7 +38,7 @@ internal class NetmapServiceProvider : ContextAccessor return settings; } - internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) + internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; var request = new LocalNodeInfoRequest @@ -72,7 +70,7 @@ internal class NetmapServiceProvider : ContextAccessor return response; } - internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) + internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 95106b7..bf8acf2 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,23 +1,23 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Google.Protobuf; - using FrostFS.Object; using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Extensions; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; using FrostFS.Session; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ClientV2.Extensions; -using FrostFS.SDK.ClientV2.Parameters; -using System.Buffers; + +using Google.Protobuf; namespace FrostFS.SDK.ClientV2; -internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) : ContextAccessor(env), ISessionProvider +internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) + : ContextAccessor(env), ISessionProvider { readonly SessionProvider sessions = new (env); @@ -26,16 +26,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return await sessions.GetOrCreateSession(args, ctx); } - internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) + internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; + var request = new HeadRequest { Body = new HeadRequest.Types.Body { Address = new Address { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.ContainerID, ObjectId = args.ObjectId.ToMessage() } } @@ -119,7 +120,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Verifier.CheckResponse(response); } - internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { var ctx = args.Context!; var request = new SearchRequest @@ -149,11 +150,11 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C await foreach (var oid in objectsIds) { - yield return ObjectId.FromHash(oid.Value.ToByteArray()); + yield return FrostFsObjectId.FromHash(oid.Value.ToByteArray()); } } - internal async Task PutObjectAsync(PrmObjectPut args) + internal async Task PutObjectAsync(PrmObjectPut args) { if (args.Header == null) throw new ArgumentException("Value cannot be null", nameof(args.Header)); @@ -174,17 +175,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } } - internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) + internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) { var ctx = args.Context!; var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx); var request = new PutSingleRequest { - Body = new PutSingleRequest.Types.Body() - { - Object = grpcObject - } + Body = new () { Object = grpcObject } }; var sessionToken = await GetOrCreateSession(args, ctx); @@ -202,15 +200,15 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C Verifier.CheckResponse(response); - return ObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); + return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } - private async Task PutClientCutObject(PrmObjectPut args) + private async Task PutClientCutObject(PrmObjectPut args) { var ctx = args.Context!; var tokenRaw = await GetOrCreateSession(args, ctx); - var token = new ModelsV2.SessionToken(tokenRaw.Serialize()); + var token = new FrostFsSessionToken(tokenRaw.Serialize()); args.SessionToken = token; @@ -237,9 +235,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; var objectsCount = fullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; - List sentObjectIds = new(objectsCount); + List sentObjectIds = new(objectsCount); - Split? split = null; + FrostFsSplit? split = null; // keep attributes for the large object var attributes = args.Header!.Attributes; @@ -249,7 +247,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { if (split == null) { - split = new Split(); + split = new FrostFsSplit(); args.Header!.Attributes = []; } @@ -266,7 +264,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C // send the last part and create linkObject if (sentObjectIds.Count > 0) { - var largeObjectHeader = new ObjectHeader(header.ContainerId) { PayloadLength = fullLength }; + var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId) { PayloadLength = fullLength }; largeObjectHeader.Attributes.AddRange(attributes); @@ -276,7 +274,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C sentObjectIds.Add(result.ObjectId); - var linkObject = new LinkObject(header.ContainerId, split!.SplitId, largeObjectHeader) + var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader) .AddChildren(sentObjectIds); _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context}); @@ -290,9 +288,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C return singlePartResult.ObjectId; } - struct PutObjectResult(ObjectId objectId, int objectSize) + struct PutObjectResult(FrostFsObjectId objectId, int objectSize) { - public ObjectId ObjectId = objectId; + public FrostFsObjectId ObjectId = objectId; public int ObjectSize = objectSize; } @@ -359,7 +357,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var response = await stream.Close(); Verifier.CheckResponse(response); - return new PutObjectResult(ObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); + return new PutObjectResult(FrostFsObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); } finally { @@ -374,10 +372,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { var header = args.Header!; - header.OwnerId = ctx.OwnerId; - header.Version = ctx.Version; + header.OwnerId ??= ctx.OwnerId; + header.Version ??= ctx.Version; - var grpcHeader = header.ToMessage(); + var grpcHeader = header.GetHeader(); if (header.Split != null) { diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 104fa4c..4498b3c 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; + using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.ClientV2; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs index 75edcc6..05797de 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs @@ -4,5 +4,3 @@ internal class ContextAccessor(ClientEnvironment context) { protected ClientEnvironment Context { get; set; } = context; } - - diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index ccfec71..d1cc432 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -1,6 +1,6 @@ - using System.Threading.Tasks; -using FrostFS.SDK.ClientV2.Parameters; + +using FrostFS.SDK.ClientV2; namespace FrostFS.SDK.ClientV2; diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 14ccd40..1ac5183 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -1,20 +1,20 @@ -using FrostFS.SDK.ModelsV2; -using Grpc.Net.Client; using System; -using System.Security.Cryptography; using System.Buffers; +using System.Security.Cryptography; + +using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(Client client, ECDsa? key, OwnerId? owner, GrpcChannel channel, ModelsV2.Version version) : IDisposable +public class ClientEnvironment(Client client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { private ArrayPool _arrayPool; - internal OwnerId? Owner { get; } = owner; + internal FrostFsOwner? Owner { get; } = owner; internal GrpcChannel Channel { get; private set; } = channel; - internal ModelsV2.Version Version { get; } = version; + internal FrostFsVersion Version { get; } = version; internal NetworkSettings? NetworkSettings { get; set; } @@ -42,7 +42,7 @@ public class ClientEnvironment(Client client, ECDsa? key, OwnerId? owner, GrpcCh { if (disposing) { - Channel.Dispose(); + Channel?.Dispose(); } } } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs index 0dd931e..05d957c 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Object.cs @@ -1,7 +1,5 @@ - using System; using System.Collections.Generic; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2.Extensions; @@ -21,29 +19,29 @@ public static class ObjectExtensions public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value) { - obj.AddAttribute(new ObjectAttribute(key, value)); + obj.AddAttribute(new FrostFsAttribute(key, value)); return obj; } - public static FrostFsObject AddAttribute(this FrostFsObject obj, ObjectAttribute attribute) + public static FrostFsObject AddAttribute(this FrostFsObject obj, FrostFsAttribute attribute) { obj.Header.Attributes.Add(attribute); return obj; } - public static FrostFsObject AddAttributes(this FrostFsObject obj, IEnumerable attributes) + public static FrostFsObject AddAttributes(this FrostFsObject obj, IEnumerable attributes) { obj.Header.Attributes.AddRange(attributes); return obj; } - public static FrostFsObject SetSplit(this FrostFsObject obj, Split? split) + public static FrostFsObject SetSplit(this FrostFsObject obj, FrostFsSplit? split) { obj.Header.Split = split; return obj; } - public static LinkObject AddChildren(this LinkObject linkObject, IEnumerable objectIds) + public static FrostFsLinkObject AddChildren(this FrostFsLinkObject linkObject, IEnumerable objectIds) { linkObject.Header.Split!.Children.AddRange(objectIds); return linkObject; diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs index 647a7ad..07c4793 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Grpc.Core; using FrostFS.Object; -using FrostFS.SDK.ModelsV2; using System.Threading; namespace FrostFS.SDK.ClientV2; @@ -33,7 +32,7 @@ public class ObjectReader(AsyncServerStreamingCall call) : IObjectR }; } - public async Task ReadChunk(CancellationToken cancellationToken = default) + public async Task?> ReadChunk(CancellationToken cancellationToken = default) { if (!await Call.ResponseStream.MoveNext(cancellationToken)) return null; @@ -44,14 +43,14 @@ public class ObjectReader(AsyncServerStreamingCall call) : IObjectR if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk) throw new InvalidOperationException("unexpected message type"); - return response.Body.Chunk.ToByteArray(); + return response.Body.Chunk.Memory; } public void Dispose() { if (!disposed) { - Call.Dispose(); + Call?.Dispose(); GC.SuppressFinalize(this); disposed = true; diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs index 11858c3..9350282 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs @@ -30,6 +30,6 @@ internal class ObjectStreamer(AsyncClientStreamingCall public void Dispose() { - Call.Dispose(); + Call?.Dispose(); } } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index 321724a..64c09cd 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -6,13 +6,12 @@ using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; namespace FrostFS.SDK.ClientV2; internal class ObjectTools { - internal static ObjectId CalculateObjectId(ObjectHeader header, Context ctx) + internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx) { var grpcHeader = CreateHeader(header, [], ctx); @@ -24,10 +23,10 @@ internal class ObjectTools internal static Object.Object CreateObject(FrostFsObject @object, Context ctx) { - @object.Header.OwnerId = ctx.OwnerId; - @object.Header.Version = ctx.Version; + @object.Header.OwnerId ??= ctx.OwnerId; + @object.Header.Version ??= ctx.Version; - var grpcHeader = @object.Header.ToMessage(); + var grpcHeader = @object.Header.GetHeader(); grpcHeader.PayloadLength = (ulong)@object.Payload.Length; grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); @@ -47,18 +46,21 @@ internal class ObjectTools obj.Signature = new Refs.Signature { - Key = ctx.PublicKeyCache, + Key = ctx.GetPublicKeyCache(), Sign = ByteString.CopyFrom(ctx.Key.SignData(obj.ObjectId.ToByteArray())), }; return obj; } - internal static void SetSplitValues(Header grpcHeader, ModelsV2.Split split, Context ctx) + internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, Context ctx) { + if (split == null) + return; + grpcHeader.Split = new Header.Types.Split { - SplitId = split.SplitId != null ? ByteString.CopyFrom(split.SplitId.ToBinary()) : null + SplitId = split.SplitId?.GetSplitId() }; if (split.Children != null && split.Children.Count != 0) @@ -72,7 +74,7 @@ internal class ObjectTools grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Refs.Signature { - Key = ctx.PublicKeyCache, + Key = ctx.GetPublicKeyCache(), Sign = ByteString.CopyFrom(ctx.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), }; @@ -82,13 +84,13 @@ internal class ObjectTools grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(ObjectHeader header, byte[]? payload, Context ctx) + internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, Context ctx) { - var grpcHeader = header.ToMessage(); - - grpcHeader.OwnerId = ctx.OwnerId.ToMessage(); - grpcHeader.Version = ctx.Version.ToMessage(); + header.OwnerId ??= ctx.OwnerId; + header.Version ??= ctx.Version; + var grpcHeader = header.GetHeader(); + if (payload != null) // && payload.Length > 0 grpcHeader.PayloadHash = Sha256Checksum(payload); diff --git a/src/FrostFS.SDK.ClientV2/Tools/Range.cs b/src/FrostFS.SDK.ClientV2/Tools/Range.cs index 9c989c1..eae7b50 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Range.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Range.cs @@ -24,10 +24,8 @@ namespace System public Index(int value, bool fromEnd = false) { if (value < 0) - { throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - + if (fromEnd) _value = ~value; else @@ -52,10 +50,8 @@ namespace System public static Index FromStart(int value) { if (value < 0) - { throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - + return new Index(value); } @@ -65,10 +61,8 @@ namespace System public static Index FromEnd(int value) { if (value < 0) - { throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - + return new Index(~value); } @@ -77,14 +71,7 @@ namespace System { get { - if (_value < 0) - { - return ~_value; - } - else - { - return _value; - } + return _value < 0 ? ~_value : _value; } } @@ -202,6 +189,7 @@ namespace System { int start; var startIndex = Start; + if (startIndex.IsFromEnd) start = length - startIndex.Value; else @@ -209,16 +197,15 @@ namespace System int end; var endIndex = End; + if (endIndex.IsFromEnd) end = length - endIndex.Value; else end = endIndex.Value; if ((uint)end > (uint)length || (uint)start > (uint)end) - { throw new ArgumentOutOfRangeException(nameof(length)); - } - + return (start, end - start); } } @@ -234,10 +221,8 @@ namespace System.Runtime.CompilerServices public static T[] GetSubArray(T[] array, Range range) { if (array == null) - { throw new ArgumentNullException(nameof(array)); - } - + (int offset, int length) = range.GetOffsetAndLength(array.Length); if (default(T) != null || typeof(T[]) == array.GetType()) @@ -245,10 +230,8 @@ namespace System.Runtime.CompilerServices // We know the type of the array to be exactly T[]. if (length == 0) - { return []; - } - + var dest = new T[length]; Array.Copy(array, offset, dest, 0, length); return dest; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index 2bbba92..c12d280 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -5,7 +5,6 @@ using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index f5057eb..c2d80f7 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -1,6 +1,11 @@ using System; using System.Security.Cryptography; +using FrostFS.Refs; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; + using Google.Protobuf; using Org.BouncyCastle.Asn1.Sec; @@ -9,11 +14,6 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; -using FrostFS.Refs; -using FrostFS.SDK.Cryptography; -using FrostFS.Session; -using FrostFS.SDK.ProtosV2.Interfaces; - namespace FrostFS.SDK.ClientV2; public static class RequestSigner diff --git a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs b/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs index 6bc72df..1bcf398 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; -using Grpc.Core; - using FrostFS.Object; using FrostFS.Refs; -using System.Threading; + +using Grpc.Core; namespace FrostFS.SDK.ClientV2; @@ -29,6 +29,6 @@ internal class SearchReader(AsyncServerStreamingCall call) : IDi public void Dispose() { - Call.Dispose(); + Call?.Dispose(); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index bd8ddec..2640e45 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -1,6 +1,12 @@ using System; using System.Security.Cryptography; +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; + using Google.Protobuf; using Org.BouncyCastle.Asn1.Sec; @@ -9,12 +15,6 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; -using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.Cryptography; -using FrostFS.Session; -using FrostFS.SDK.ProtosV2.Interfaces; - namespace FrostFS.SDK.ClientV2; public static class Verifier diff --git a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs index c2abf26..80ba47a 100644 --- a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class ClientSettings { @@ -13,7 +13,7 @@ public class ClientSettings public virtual void Validate() { var errors = CheckFields(); - if (errors != null) + if (errors != null) ThrowException(errors); } @@ -28,7 +28,7 @@ public class ClientSettings } protected static void ThrowException(List errors) - { + { StringBuilder messages = new(); foreach (var error in errors) @@ -48,7 +48,7 @@ public class SingleOwnerClientSettings : ClientSettings { var errors = CheckFields(); if (errors != null) - ThrowException(errors); + ThrowException(errors); } protected List? CheckFields() @@ -59,5 +59,5 @@ public class SingleOwnerClientSettings : ClientSettings (errors ??= []).Add(string.Format(errorTemplate, nameof(Key))); return errors; - } + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Containers/Container.cs b/src/FrostFS.SDK.ModelsV2/Containers/Container.cs deleted file mode 100644 index 02543b3..0000000 --- a/src/FrostFS.SDK.ModelsV2/Containers/Container.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; - -namespace FrostFS.SDK.ModelsV2; - -public class Container(BasicAcl basicAcl, PlacementPolicy placementPolicy) -{ - public Guid Nonce { get; set; } = Guid.NewGuid(); - public BasicAcl BasicAcl { get; set; } = basicAcl; - public PlacementPolicy PlacementPolicy { get; set; } = placementPolicy; - public Version? Version { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs index 349eb1c..4543dcd 100644 --- a/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs +++ b/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class ContainerId(string id) { diff --git a/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs b/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs new file mode 100644 index 0000000..45d3b58 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs @@ -0,0 +1,11 @@ +using System; + +namespace FrostFS.SDK; + +public class FrostFsContainer(BasicAcl basicAcl, FrostFsPlacementPolicy placementPolicy) +{ + public Guid Nonce { get; set; } = Guid.NewGuid(); + public BasicAcl BasicAcl { get; set; } = basicAcl; + public FrostFsPlacementPolicy PlacementPolicy { get; set; } = placementPolicy; + public FrostFsVersion? Version { get; set; } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs index 9418039..dd3252b 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs +++ b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs @@ -1,6 +1,6 @@ using System.ComponentModel; -namespace FrostFS.SDK.ModelsV2.Enums; +namespace FrostFS.SDK; public enum BasicAcl { diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs new file mode 100644 index 0000000..1fdeb60 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK; + +public enum FrostFsObjectMatchType +{ + Unspecified = 0, + Equals = 1, + NotEquals = 2, + KeyAbsent = 3, + StartsWith = 4 +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs new file mode 100644 index 0000000..793fe2e --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK; + +public enum FrostFsObjectType +{ + Regular = 0, + Tombstone = 1, + Lock = 3 +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs new file mode 100644 index 0000000..7eb185c --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs @@ -0,0 +1,22 @@ +namespace FrostFS.SDK; + +public enum FrostFsStatusCode +{ + Success = 0, + Internal = 1024, + WrongMagicNumber = 1025, + SignatureVerificationFailure = 1026, + NodeUnderMaintenance = 1027, + ObjectAccessDenied = 2048, + ObjectNotFound = 2049, + ObjectLocked = 2050, + LockNotRegularObject = 2051, + ObjectAlreadyRemoved = 2052, + OutOfRange = 2053, + ContainerNotFound = 3072, + EAclNotFound = 3073, + ContainerAccessDenied = 3074, + TokenNotFound = 4096, + TokenExpired = 4097, + ApeManagerAccessDenied = 5120 +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs b/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs index 2293c0f..2821e55 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs +++ b/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ModelsV2.Enums; +namespace FrostFS.SDK; public enum NodeState { diff --git a/src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs b/src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs deleted file mode 100644 index 6f8a905..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK.ModelsV2.Enums; - -public enum ObjectType -{ - Regular = 0, - Tombstone = 1, - Lock = 3 -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs b/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs index bd6c41d..4b5634c 100644 --- a/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs +++ b/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs @@ -1,7 +1,8 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; -public enum SignatureScheme { +public enum SignatureScheme +{ EcdsaSha512, EcdsaRfc6979Sha256, EcdsaRfc6979Sha256WalletConnect - } +} diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs index a6cbcbf..706b3f2 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs @@ -1,7 +1,7 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class CallStatistics -{ +{ public string? MethodName { get; set; } public long ElapsedMicroSeconds { get; set; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs index 872038b..82017f9 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs @@ -1,7 +1,7 @@ using FrostFS.SDK.Cryptography; using System; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class CheckSum { diff --git a/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs b/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs index 4b72aa1..332a3e3 100644 --- a/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs +++ b/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class Constants { diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs new file mode 100644 index 0000000..9581b21 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace FrostFS.SDK; + +public class FrostFsNodeInfo( + FrostFsVersion version, + NodeState state, + IReadOnlyCollection addresses, + IReadOnlyDictionary attributes, + ReadOnlyMemory publicKey) +{ + public NodeState State { get; private set; } = state; + public FrostFsVersion Version { get; private set; } = version; + public IReadOnlyCollection Addresses { get; private set; } = addresses; + public IReadOnlyDictionary Attributes { get; private set; } = attributes; + public ReadOnlyMemory PublicKey { get; private set; } = publicKey; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs new file mode 100644 index 0000000..410598c --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; + +namespace FrostFS.SDK; + +public class FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) : IComparable +{ + public FrostFsReplica[] Replicas { get; private set; } = replicas; + public bool Unique { get; private set; } = unique; + + public int CompareTo(FrostFsPlacementPolicy other) + { + var notEqual = other == null + || Unique != other.Unique + || Replicas.Length != other.Replicas.Length; + + if (notEqual) + return 1; + + foreach (var replica in Replicas) + { + if (!other!.Replicas.Any(r => r.Count == replica.Count && r.Selector == replica.Selector)) + return 1; + } + + return 0; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs new file mode 100644 index 0000000..8fd1bd9 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs @@ -0,0 +1,15 @@ +namespace FrostFS.SDK; + +public class FrostFsReplica +{ + public int Count { get; set; } + public string Selector { get; set; } + + public FrostFsReplica(int count, string? selector = null) + { + selector ??= string.Empty; + + Count = count; + Selector = selector; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/Version.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs similarity index 64% rename from src/FrostFS.SDK.ModelsV2/Netmap/Version.cs rename to src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs index 88bdee2..06c3ca2 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/Version.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs @@ -1,11 +1,11 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; -public class Version(int major, int minor) +public class FrostFsVersion(int major, int minor) { public int Major { get; set; } = major; public int Minor { get; set; } = minor; - public bool IsSupported(Version version) + public bool IsSupported(FrostFsVersion version) { return Major == version.Major; } diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs index fb8dba1..a371564 100644 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs +++ b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; -namespace FrostFS.SDK.ModelsV2.Netmap; +namespace FrostFS.SDK; -public class NetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) +public class NetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) { public ulong Epoch { get; private set; } = epoch; - public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; + public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs index 1443abf..e382ec5 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs @@ -1,7 +1,6 @@ using System; -using FrostFS.SDK.ModelsV2.Enums; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class FrostFsObject { @@ -11,7 +10,7 @@ public class FrostFsObject /// public FrostFsObject(ObjectHeader header) { - Header = header; + Header = header; } /// @@ -19,17 +18,17 @@ public class FrostFsObject /// /// /// - public FrostFsObject(ContainerId container, ObjectType objectType = ObjectType.Regular) + public FrostFsObject(ContainerId container, FrostFsObjectType objectType = FrostFsObjectType.Regular) { - Header = new ObjectHeader(containerId: container, type: objectType); + Header = new ObjectHeader(containerId: container, type: objectType); } - + /// /// Header contains metadata for the object /// /// public ObjectHeader Header { get; set; } - + /// /// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed. /// @@ -50,7 +49,7 @@ public class FrostFsObject /// /// Reader for received data public IObjectReader? ObjectReader { get; set; } - + /// /// Applied only for the last Object in chain in case of manual multipart uploading /// @@ -74,11 +73,11 @@ public class LargeObject(ContainerId container) : FrostFsObject(container) public class LinkObject : FrostFsObject { - public LinkObject(ContainerId containerId, SplitId splitId, ObjectHeader largeObjectHeader) : base (containerId) + public LinkObject(ContainerId containerId, SplitId splitId, ObjectHeader largeObjectHeader) : base(containerId) { Header!.Split = new Split(splitId) { - ParentHeader = largeObjectHeader + ParentHeader = largeObjectHeader }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs index 4c5ce3d..51211fd 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs @@ -2,9 +2,9 @@ using System; using System.Threading; using System.Threading.Tasks; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public interface IObjectReader : IDisposable { - Task ReadChunk(CancellationToken cancellationToken = default); + Task?> ReadChunk(CancellationToken cancellationToken = default); } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs index 92d38b2..110db12 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class ObjectAttribute(string key, string value) { diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs index fbed30c..40f654d 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs @@ -1,18 +1,16 @@ -using FrostFS.SDK.ModelsV2.Enums; - -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public interface IObjectFilter { - public ObjectMatchType MatchType { get; set; } + public FrostFsObjectMatchType MatchType { get; set; } public string Key { get; set; } - + string? GetSerializedValue(); } -public abstract class ObjectFilter(ObjectMatchType matchType, string key, T value) : IObjectFilter +public abstract class ObjectFilter(FrostFsObjectMatchType matchType, string key, T value) : IObjectFilter { - public ObjectMatchType MatchType { get; set; } = matchType; + public FrostFsObjectMatchType MatchType { get; set; } = matchType; public string Key { get; set; } = key; public T Value { get; set; } = value; @@ -29,85 +27,85 @@ public abstract class ObjectFilter(ObjectMatchType matchType, string key, T v /// Match type /// Attribute key /// Attribute value -public class FilterByAttribute(ObjectMatchType matchType, string key, string value) : ObjectFilter(matchType, key, value) { } +public class FilterByAttribute(FrostFsObjectMatchType matchType, string key, string value) : ObjectFilter(matchType, key, value) { } /// /// Creates filter to search by ObjectId /// /// Match type /// ObjectId -public class FilterByObjectId(ObjectMatchType matchType, ObjectId objectId) : ObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } +public class FilterByObjectId(FrostFsObjectMatchType matchType, ObjectId objectId) : ObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } /// /// Creates filter to search by OwnerId /// /// Match type /// ObjectId -public class FilterByOwnerId(ObjectMatchType matchType, OwnerId ownerId) : ObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) {} +public class FilterByOwnerId(FrostFsObjectMatchType matchType, OwnerId ownerId) : ObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) { } /// /// Creates filter to search by Version /// /// Match type /// Version -public class FilterByVersion(ObjectMatchType matchType, Version version) : ObjectFilter(matchType, Constants.FilterHeaderVersion, version) {} +public class FilterByVersion(FrostFsObjectMatchType matchType, FrostFsVersion version) : ObjectFilter(matchType, Constants.FilterHeaderVersion, version) { } /// /// Creates filter to search by ContainerId /// /// Match type /// ContainerId -public class FilterByContainerId(ObjectMatchType matchType, ContainerId containerId) : ObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) {} +public class FilterByContainerId(FrostFsObjectMatchType matchType, ContainerId containerId) : ObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) { } /// /// Creates filter to search by creation Epoch /// /// Match type /// Creation Epoch -public class FilterByEpoch(ObjectMatchType matchType, ulong epoch) : ObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) {} +public class FilterByEpoch(FrostFsObjectMatchType matchType, ulong epoch) : ObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) { } /// /// Creates filter to search by Payload Length /// /// Match type /// Payload Length -public class FilterByPayloadLength(ObjectMatchType matchType, ulong payloadLength) : ObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) {} +public class FilterByPayloadLength(FrostFsObjectMatchType matchType, ulong payloadLength) : ObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { } /// /// Creates filter to search by Payload Hash /// /// Match type /// Payload Hash -public class FilterByPayloadHash(ObjectMatchType matchType, CheckSum payloadHash) : ObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) {} +public class FilterByPayloadHash(FrostFsObjectMatchType matchType, CheckSum payloadHash) : ObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { } /// /// Creates filter to search by Parent /// /// Match type /// Parent -public class FilterByParent(ObjectMatchType matchType, ObjectId parentId) : ObjectFilter(matchType, Constants.FilterHeaderParent, parentId) {} +public class FilterByParent(FrostFsObjectMatchType matchType, ObjectId parentId) : ObjectFilter(matchType, Constants.FilterHeaderParent, parentId) { } /// /// Creates filter to search by SplitId /// /// Match type /// SplitId -public class FilterBySplitId(ObjectMatchType matchType, SplitId splitId) : ObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) {} +public class FilterBySplitId(FrostFsObjectMatchType matchType, SplitId splitId) : ObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) { } /// /// Creates filter to search by Payload Hash /// /// Match type /// Payload Hash -public class FilterByECParent(ObjectMatchType matchType, ObjectId ecParentId) : ObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) {} +public class FilterByECParent(FrostFsObjectMatchType matchType, ObjectId ecParentId) : ObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) { } /// /// Creates filter to search Root objects /// -public class FilterByRootObject() : ObjectFilter(ObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) {} +public class FilterByRootObject() : ObjectFilter(FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { } /// /// Creates filter to search objects that are physically stored on the server /// (ObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) {} +public class FilterByPhysicallyStored() : ObjectFilter(FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs index 715f903..3e3ecd3 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -using FrostFS.SDK.ModelsV2.Enums; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class ObjectHeader( ContainerId containerId, - ObjectType type = ObjectType.Regular, + FrostFsObjectType type = FrostFsObjectType.Regular, params ObjectAttribute[] attributes ) { @@ -19,9 +18,9 @@ public class ObjectHeader( public byte[]? PayloadCheckSum { get; set; } - public ObjectType ObjectType { get; set; } = type; + public FrostFsObjectType ObjectType { get; set; } = type; - public Version? Version { get; set; } + public FrostFsVersion? Version { get; set; } public Split? Split { get; set; } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs index 74e6bfb..9072361 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs @@ -2,7 +2,7 @@ using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class ObjectId(string id) { @@ -12,7 +12,7 @@ public class ObjectId(string id) { if (hash.Length != Constants.Sha256HashLength) throw new FormatException("ObjectID must be a sha256 hash."); - + return new ObjectId(Base58.Encode(hash)); } diff --git a/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs b/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs index b930bb7..5da87ef 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs @@ -2,7 +2,7 @@ using System.Security.Cryptography; using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class OwnerId(string id) { diff --git a/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs b/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs index 111f632..8af45c1 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs @@ -1,30 +1,35 @@ using FrostFS.SDK.Cryptography; + +using Google.Protobuf; + using System; -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class SplitId { - private Guid id; + private readonly Guid id; + + ByteString? _message; public SplitId() { - this.id = Guid.NewGuid(); + id = Guid.NewGuid(); } public SplitId(Guid guid) { - this.id = guid; + id = guid; } private SplitId(byte[] binary) { - this.id = new Guid(binary); + id = new Guid(binary); } private SplitId(string str) { - this.id = new Guid(str); + id = new Guid(str); } public static SplitId CreateFromBinary(byte[] binaryData) @@ -35,18 +40,23 @@ public class SplitId public static SplitId CreateFromString(string stringData) { return new SplitId(stringData); - } + } public override string ToString() { - return this.id.ToString(); + return id.ToString(); } public byte[]? ToBinary() { - if (this.id == Guid.Empty) + if (id == Guid.Empty) return null; - return this.id.ToBytes(); + return id.ToBytes(); + } + + public ByteString? GetMessage() + { + return _message ??= ByteString.CopyFrom(ToBinary()); } } diff --git a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs index 3fb55df..64b3515 100644 --- a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs +++ b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; - -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; public class Split(SplitId splitId) { @@ -14,8 +13,8 @@ public class Split(SplitId splitId) public ObjectId? Parent { get; set; } public ObjectId? Previous { get; set; } - - public Signature? ParentSignature { get; set; } + + public FrostFsSignature? ParentSignature { get; set; } public ObjectHeader? ParentHeader { get; set; } diff --git a/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs new file mode 100644 index 0000000..b5e5831 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs @@ -0,0 +1,14 @@ +namespace FrostFS.SDK; + +public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null) +{ + public FrostFsStatusCode Code { get; set; } = code; + public string Message { get; set; } = message ?? string.Empty; + + public bool IsSuccess => Code == FrostFsStatusCode.Success; + + public override string ToString() + { + return $"Response status: {Code}. Message: {Message}."; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs b/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs new file mode 100644 index 0000000..d7ca6b6 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK; + +public class FrostFsSignature +{ + public byte[]? Key { get; set; } + + public byte[]? Sign { get; set; } + + public SignatureScheme Scheme { get; set; } +} diff --git a/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs b/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs index d2b84a2..36dad09 100644 --- a/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs +++ b/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs @@ -1,15 +1,15 @@ -namespace FrostFS.SDK.ModelsV2; +namespace FrostFS.SDK; -public class MetaHeader(Version version, int epoch, int ttl) +public class MetaHeader(FrostFsVersion version, int epoch, int ttl) { - public Version Version { get; set; } = version; + public FrostFsVersion Version { get; set; } = version; public int Epoch { get; set; } = epoch; public int Ttl { get; set; } = ttl; public static MetaHeader Default() { return new MetaHeader( - new Version( + new FrostFsVersion( major: 2, minor: 13 ), diff --git a/src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs b/src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs deleted file mode 100644 index f58e1bb..0000000 --- a/src/FrostFS.SDK.ModelsV2/Response/ResponseStatus.cs +++ /dev/null @@ -1,16 +0,0 @@ -using FrostFS.SDK.ModelsV2.Enums; - -namespace FrostFS.SDK.ModelsV2; - -public class ResponseStatus(StatusCode code, string? message = null) -{ - public StatusCode Code { get; set; } = code; - public string Message { get; set; } = message ?? string.Empty; - - public bool IsSuccess => Code == StatusCode.Success; - - public override string ToString() - { - return $"Response status: {Code}. Message: {Message}."; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Response/Signature.cs b/src/FrostFS.SDK.ModelsV2/Response/Signature.cs deleted file mode 100644 index 1d36e1c..0000000 --- a/src/FrostFS.SDK.ModelsV2/Response/Signature.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK.ModelsV2; - -public class Signature -{ - public byte[]? Key { get; set; } - public byte[]? Sign { get; set; } - public SignatureScheme Scheme { get; set; } -} diff --git a/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs new file mode 100644 index 0000000..8954780 --- /dev/null +++ b/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK; + +public class FrostFsSessionToken(byte[] token) +{ + public byte[] Token { get; private set; } = token; +} diff --git a/src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs b/src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs deleted file mode 100644 index 2865ca3..0000000 --- a/src/FrostFS.SDK.ModelsV2/Session/SessionToken.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FrostFS.SDK.ModelsV2; - -public class SessionToken(byte[] token) -{ - public byte[] Token { get; private set; } = token; -} diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 0af0e70..fcfcb39 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,14 +1,11 @@ -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ModelsV2.Enums; using Google.Protobuf; -using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2.Parameters; namespace FrostFS.SDK.Tests; @@ -29,8 +26,8 @@ public abstract class ContainerTestsBase Mocker = new ContainerMocker(this.key) { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13), ContainerGuid = Guid.NewGuid() }; } @@ -52,7 +49,7 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void CreateContainerTest() { - var param = new PrmContainerCreate(new ModelsV2.Container(BasicAcl.PublicRW, Mocker.PlacementPolicy)); + var param = new PrmContainerCreate(new FrostFsContainerInfo(BasicAcl.PublicRW, Mocker.PlacementPolicy)); var result = await GetClient().CreateContainerAsync(param); @@ -64,7 +61,7 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void GetContainerTest() { - var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); Mocker.Acl = BasicAcl.PublicRO; @@ -102,7 +99,7 @@ public class ContainerTest : ContainerTestsBase public async void DeleteContainerAsyncTest() { Mocker.ReturnContainerRemoved = true; - var cid = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); await GetClient().DeleteContainerAsync(new PrmContainerDelete(cid)); diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs new file mode 100644 index 0000000..cc6b438 --- /dev/null +++ b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs @@ -0,0 +1,39 @@ +using Grpc.Core; +using Grpc.Core.Interceptors; + +using System.Diagnostics; + +namespace FrostFS.SDK.SmokeTests; + +public class MetricsInterceptor() : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private static async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var watch = new Stopwatch(); + watch.Start(); + + var response = await call.ResponseAsync; + + watch.Stop(); + + // Do something with call info + // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + + return response; + } +} diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 3603a92..a8ddbbd 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -1,17 +1,18 @@ +using System.Security.Cryptography; + using FrostFS.Object; -using FrostFS.Session; -using Google.Protobuf; -using Grpc.Core; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using System.Security.Cryptography; +using FrostFS.Session; + +using Google.Protobuf; + +using Grpc.Core; namespace FrostFS.SDK.Tests; -public class AsyncStreamReaderMock(string key, ObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader +public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) : ServiceBase(key), IAsyncStreamReader { public GetResponse Current { diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs index fb1fe2d..56746e4 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -21,8 +21,9 @@ public class ClientStreamWriter : IClientStreamWriter } public Task WriteAsync(IRequest message) - { - Messages.Add(message); + { + Object.PutRequest pr = new((Object.PutRequest)message); + Messages.Add(pr); return Task.CompletedTask; } } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 7cf2690..4235f71 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -3,8 +3,6 @@ using FrostFS.Container; using Moq; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.Session; using Google.Protobuf; @@ -19,18 +17,18 @@ public abstract class ServiceBase(string key) { public string StringKey { get; private set; } = key; public ECDsa Key { get; private set; } = key.LoadWif(); - public ModelsV2.Version Version { get; set; } = DefaultVersion; + public FrostFsVersion Version { get; set; } = DefaultVersion; public BasicAcl Acl { get; set; } = DefaultAcl; - public PlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; + public FrostFsPlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; - public static ModelsV2.Version DefaultVersion { get; } = new(2, 13); + public static FrostFsVersion DefaultVersion { get; } = new(2, 13); public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; - public static PlacementPolicy DefaultPlacementPolicy { get; } = new PlacementPolicy(true, new Replica(1)); + public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); public Metadata Metadata { get; protected set; } public DateTime? DateTime { get; protected set; } - public CancellationToken CancellationToken { get; protected set; } + public CancellationToken CancellationToken { get; protected set; } public CancellationTokenSource CancellationTokenSource { get; protected set; } = new CancellationTokenSource(); public static Metadata ResponseMetaData => []; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 34f6173..c76cc9f 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -5,8 +5,7 @@ using Moq; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; + using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.Session; using FrostFS.Refs; diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 099398d..7e26a3e 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -2,7 +2,6 @@ using Google.Protobuf; using Grpc.Core; using Moq; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ModelsV2; using FrostFS.Object; using FrostFS.SDK.ClientV2.Mappers.GRPC; using System.Security.Cryptography; @@ -184,14 +183,13 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) () => { }); }); } - return mock; } - public ObjectId? ObjectId { get; set; } + public FrostFsObjectId? ObjectId { get; set; } - public ObjectHeader? ObjectHeader { get; set; } + public FrostFsObjectHeader? ObjectHeader { get; set; } public Header? HeadResponse { get; set; } diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 0fce906..11a7ff0 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -1,12 +1,13 @@ using FrostFS.Netmap; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; + using Google.Protobuf; + using Microsoft.Extensions.Options; + using System.Security.Cryptography; namespace FrostFS.SDK.Tests; @@ -17,10 +18,10 @@ public abstract class NetworkTestsBase protected IOptions Settings { get; set; } - protected ModelsV2.Version Version { get; set; } = new ModelsV2.Version(2, 13); + protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13); protected ECDsa ECDsaKey { get; set; } - protected OwnerId OwnerId { get; set; } + protected FrostFsOwner OwnerId { get; set; } protected NetworkMocker Mocker { get; set; } protected NetworkTestsBase() @@ -32,7 +33,7 @@ public abstract class NetworkTestsBase }); ECDsaKey = key.LoadWif(); - OwnerId = OwnerId.FromKey(ECDsaKey); + OwnerId = FrostFsOwner.FromKey(ECDsaKey); Mocker = new NetworkMocker(this.key); } diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index db20741..37fe81a 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -2,13 +2,13 @@ using FrostFS.Refs; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; + using Google.Protobuf; + using Microsoft.Extensions.Options; + using System.Security.Cryptography; using System.Text; @@ -19,7 +19,7 @@ public abstract class ObjectTestsBase protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; protected IOptions Settings { get; set; } - protected ContainerId ContainerId { get; set; } + protected FrostFsContainerId ContainerId { get; set; } protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key); @@ -38,18 +38,20 @@ public abstract class ObjectTestsBase Mocker = new ObjectMocker(key) { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13), + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13), ContainerGuid = Guid.NewGuid() }; - ContainerId = new ContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - Mocker.ObjectHeader = new(ContainerId, ModelsV2.Enums.ObjectType.Regular, [new ObjectAttribute("k", "v")]) - { - Version = new ModelsV2.Version(2, 13), - OwnerId = OwnerId.FromKey(ecdsaKey) - }; + Mocker.ObjectHeader = new( + ContainerId, + FrostFsObjectType.Regular, + [new FrostFsAttribute("k", "v")], + null, + FrostFsOwner.FromKey(ecdsaKey), + new FrostFsVersion(2, 13)); } protected IFrostFSClient GetClient() @@ -75,8 +77,8 @@ public class ObjectTest : ObjectTestsBase var ctx = new Context { Key = ecdsaKey, - OwnerId = OwnerId.FromKey(ecdsaKey), - Version = new ModelsV2.Version(2, 13) }; + OwnerId = FrostFsOwner.FromKey(ecdsaKey), + Version = new FrostFsVersion(2, 13) }; var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx); @@ -218,7 +220,7 @@ public class ObjectTest : ObjectTestsBase Assert.Equal("k", linkObject.Header.Split.ParentHeader.Attributes[0].Key); Assert.Equal("v", linkObject.Header.Split.ParentHeader.Attributes[0].Value); - var modelObjId = ObjectId.FromHash(linkObject.Header.Split.Parent.Value.ToByteArray()); + var modelObjId = FrostFsObjectId.FromHash(linkObject.Header.Split.Parent.Value.ToByteArray()); Assert.Equal(result.Value, modelObjId.ToString()); } @@ -256,7 +258,7 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); - Assert.Equal(ObjectType.Regular, response.ObjectType); + Assert.Equal(FrostFsObjectType.Regular, response.ObjectType); Assert.Single(response.Attributes); diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index 8b3dc1a..7eda05e 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -1,12 +1,11 @@ using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ClientV2.Mappers.GRPC.Netmap; -using FrostFS.SDK.ClientV2.Parameters; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Netmap; + using Microsoft.Extensions.Options; + using System.Security.Cryptography; namespace FrostFS.SDK.Tests; @@ -19,7 +18,7 @@ public abstract class SessionTestsBase protected ECDsa ECDsaKey { get; set; } - protected OwnerId OwnerId { get; set; } + protected FrostFsOwner OwnerId { get; set; } protected SessionMocker Mocker { get; set; } protected SessionTestsBase() @@ -31,12 +30,12 @@ public abstract class SessionTestsBase }); ECDsaKey = key.LoadWif(); - OwnerId = OwnerId.FromKey(ECDsaKey); + OwnerId = FrostFsOwner.FromKey(ECDsaKey); Mocker = new SessionMocker(this.key) { - PlacementPolicy = new PlacementPolicy(true, new Replica(1)), - Version = new ModelsV2.Version(2, 13) + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13) }; } diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index aff3298..f344303 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -1,45 +1,14 @@ using System.Security.Cryptography; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ModelsV2; -using FrostFS.SDK.ModelsV2.Enums; -using FrostFS.SDK.ModelsV2.Netmap; using FrostFS.SDK.Cryptography; - -using Grpc.Core; using Microsoft.Extensions.Options; -using Grpc.Core.Interceptors; -using System.Diagnostics; using static FrostFS.Session.SessionToken.Types.Body; -using FrostFS.SDK.ClientV2.Parameters; -using FrostFS.SDK.Tests; +using FrostFS.SDK.ClientV2; namespace FrostFS.SDK.SmokeTests; -public abstract class SmokeTestsBase -{ - protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected readonly string url = "http://172.23.32.4:8080"; - - protected ECDsa Key { get; } - - protected OwnerId OwnerId { get; } - - protected ModelsV2.Version Version { get; } - - protected Context Ctx { get; } - - protected SmokeTestsBase() - { - Key = key.LoadWif(); - OwnerId = OwnerId.FromKey(Key); - Version = new ModelsV2.Version(2, 13); - - Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version }; - } -} - public class SmokeTests : SmokeTestsBase { private static readonly PrmWait lightWait = new (100, 1); @@ -134,7 +103,7 @@ public class SmokeTests : SmokeTestsBase var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); var createContainerParam = new PrmContainerCreate( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))); + new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); createContainerParam.XHeaders.Add("key1", "value1"); @@ -144,10 +113,10 @@ public class SmokeTests : SmokeTestsBase var param = new PrmObjectPut { - Header = new ObjectHeader( + Header = new FrostFsObjectHeader( containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular, - new ObjectAttribute("fileName", "test")), + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, SessionToken = token @@ -160,10 +129,10 @@ public class SmokeTests : SmokeTestsBase var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); - byte[]? chunk = null; + ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { - ms.Write(chunk); + ms.Write(chunk.Value.Span); } Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); @@ -179,7 +148,7 @@ public class SmokeTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { WaitParams = lightWait }; @@ -188,22 +157,20 @@ public class SmokeTests : SmokeTestsBase var bytes = new byte[] { 1, 2, 3 }; - var ParentHeader = new ObjectHeader( + var ParentHeader = new FrostFsObjectHeader( containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular) + type: FrostFsObjectType.Regular) { PayloadLength = 3 }; var param = new PrmObjectPut { - Header = new ObjectHeader( + Header = new FrostFsObjectHeader( containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular, - new ObjectAttribute("fileName", "test")) - { - Split = new Split(), - }, + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")], + new FrostFsSplit()), Payload = new MemoryStream(bytes), ClientCut = false }; @@ -216,30 +183,30 @@ public class SmokeTests : SmokeTestsBase var networkInfo = await client.GetNetmapSnapshotAsync(); - await CheckFilter(client, containerId, new FilterByContainerId(ObjectMatchType.Equals, containerId)); + await CheckFilter(client, containerId, new FilterByContainerId(FrostFsObjectMatchType.Equals, containerId)); - await CheckFilter(client, containerId, new FilterByOwnerId(ObjectMatchType.Equals, OwnerId.FromKey(ecdsaKey))); + await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsObjectMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - await CheckFilter(client, containerId, new FilterBySplitId(ObjectMatchType.Equals, param.Header.Split.SplitId)); + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsObjectMatchType.Equals, param.Header.Split.SplitId)); - await CheckFilter(client, containerId, new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test")); + await CheckFilter(client, containerId, new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test")); - await CheckFilter(client, containerId, new FilterByObjectId(ObjectMatchType.Equals, objectId)); + await CheckFilter(client, containerId, new FilterByObjectId(FrostFsObjectMatchType.Equals, objectId)); - await CheckFilter(client, containerId, new FilterByVersion(ObjectMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + await CheckFilter(client, containerId, new FilterByVersion(FrostFsObjectMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - await CheckFilter(client, containerId, new FilterByEpoch(ObjectMatchType.Equals, networkInfo.Epoch)); + await CheckFilter(client, containerId, new FilterByEpoch(FrostFsObjectMatchType.Equals, networkInfo.Epoch)); - await CheckFilter(client, containerId, new FilterByPayloadLength(ObjectMatchType.Equals, 3)); + await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsObjectMatchType.Equals, 3)); var checkSum = CheckSum.CreateCheckSum(bytes); - await CheckFilter(client, containerId, new FilterByPayloadHash(ObjectMatchType.Equals, checkSum)); + await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsObjectMatchType.Equals, checkSum)); await CheckFilter(client, containerId, new FilterByPhysicallyStored()); } - private static async Task CheckFilter(IFrostFSClient client, ContainerId containerId, IObjectFilter filter) + private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) { var resultObjectsCount = 0; @@ -276,14 +243,14 @@ public class SmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),[new ("testKey", "testValue")])) { Context = ctx }; - var containerId = await client.CreateContainerAsync(createContainerParam); + var createdContainer = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx }); Assert.NotNull(container); Assert.True(callbackInvoked); @@ -291,10 +258,10 @@ public class SmokeTests : SmokeTestsBase var param = new PrmObjectPut { - Header = new ObjectHeader( - containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular, - new ObjectAttribute("fileName", "test")), + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, Context = new Context @@ -305,14 +272,14 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] })) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -321,15 +288,15 @@ public class SmokeTests : SmokeTestsBase Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); - byte[]? chunk = null; + ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { - ms.Write(chunk); + ms.Write(chunk.Value.Span); } Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); @@ -361,24 +328,24 @@ public class SmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { Context = ctx }; - var containerId = await client.CreateContainerAsync(createContainerParam); + var container = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); - Assert.NotNull(container); + var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container) { Context = ctx }); + Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); var param = new PrmObjectPut { - Header = new ObjectHeader( - containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular, - new ObjectAttribute("fileName", "test")), + Header = new FrostFsObjectHeader( + containerId: container, + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, Context = new Context @@ -390,14 +357,14 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter], SessionToken = token })) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId) { SessionToken = token }); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token }); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); @@ -406,15 +373,15 @@ public class SmokeTests : SmokeTestsBase Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId) { SessionToken = token }); + var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token }); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); - byte[]? chunk = null; + ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { - ms.Write(chunk); + ms.Write(chunk.Value.Span); } Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); @@ -440,7 +407,7 @@ public class SmokeTests : SmokeTestsBase await Cleanup(client); - var createContainerParam = new PrmContainerCreate(new ModelsV2.Container(BasicAcl.PublicRW, new PlacementPolicy(true, new Replica(1)))) + var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { WaitParams = lightWait }; @@ -461,17 +428,17 @@ public class SmokeTests : SmokeTestsBase var param = new PrmObjectPut { - Header = new ObjectHeader( + Header = new FrostFsObjectHeader( containerId: containerId, - type: ModelsV2.Enums.ObjectType.Regular, - new ObjectAttribute("fileName", "test")), + type: FrostFsObjectType.Regular, + [new FrostFsAttribute("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = true }; var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(ObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) @@ -492,10 +459,10 @@ public class SmokeTests : SmokeTestsBase var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); - byte[]? chunk = null; + ReadOnlyMemory? chunk = null; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { - ms.Write(chunk); + ms.Write(chunk.Value.Span); } Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); @@ -506,7 +473,7 @@ public class SmokeTests : SmokeTestsBase var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - IAsyncEnumerator? enumerator = null; + IAsyncEnumerator? enumerator = null; do { if (deadline <= DateTime.UtcNow) @@ -554,37 +521,3 @@ public class SmokeTests : SmokeTestsBase } } } - - -public class MetricsInterceptor() : Interceptor -{ - public override AsyncUnaryCall AsyncUnaryCall( - TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) - { - var call = continuation(request, context); - - return new AsyncUnaryCall( - HandleUnaryResponse(call), - call.ResponseHeadersAsync, - call.GetStatus, - call.GetTrailers, - call.Dispose); - } - - private static async Task HandleUnaryResponse(AsyncUnaryCall call) - { - var watch = new Stopwatch(); - watch.Start(); - - var response = await call.ResponseAsync; - - watch.Stop(); - - // Do something with call info - // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; - - return response; - } -} diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs new file mode 100644 index 0000000..60e4a20 --- /dev/null +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -0,0 +1,29 @@ +using System.Security.Cryptography; + +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK.SmokeTests; + +public abstract class SmokeTestsBase +{ + protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + protected readonly string url = "http://172.23.32.4:8080"; + + protected ECDsa Key { get; } + + protected FrostFsOwner OwnerId { get; } + + protected FrostFsVersion Version { get; } + + protected Context Ctx { get; } + + protected SmokeTestsBase() + { + Key = key.LoadWif(); + OwnerId = FrostFsOwner.FromKey(Key); + Version = new FrostFsVersion(2, 13); + + Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version }; + } +} From 704ce41173df55f3cd5814ef30e31d1bc343e2b4 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 12 Sep 2024 11:56:26 +0300 Subject: [PATCH 21/65] [#23] Client: Refactoring to optimize memory usage Signed-off-by: Pavel Gross --- README.md | 2 +- .../Mappers/Object/ObjectFilterMapper.cs | 10 +++--- ...ObjectMatchType.cs => FrostFsMatchType.cs} | 2 +- .../Models/Object/FrostFsObjectFilter.cs | 32 +++++++++---------- src/FrostFS.SDK.Tests/SmokeTests.cs | 24 +++++++------- 5 files changed, 35 insertions(+), 35 deletions(-) rename src/FrostFS.SDK.ClientV2/Models/Enums/{FrostFsObjectMatchType.cs => FrostFsMatchType.cs} (77%) diff --git a/README.md b/README.md index d2e5fab..eccf0a2 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ var param = new PrmObjectPut FrostFsObjectId objectId = await client.PutObjectAsync(param); -var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); +var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId) { Filters = [filter] })) { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs index e59f51b..0303213 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -10,11 +10,11 @@ public static class ObjectFilterMapper { var objMatchTypeName = filter.MatchType switch { - FrostFsObjectMatchType.Unspecified => MatchType.Unspecified, - FrostFsObjectMatchType.Equals => MatchType.StringEqual, - FrostFsObjectMatchType.NotEquals => MatchType.StringNotEqual, - FrostFsObjectMatchType.KeyAbsent => MatchType.NotPresent, - FrostFsObjectMatchType.StartsWith => MatchType.CommonPrefix, + FrostFsMatchType.Unspecified => MatchType.Unspecified, + FrostFsMatchType.Equals => MatchType.StringEqual, + FrostFsMatchType.NotEquals => MatchType.StringNotEqual, + FrostFsMatchType.KeyAbsent => MatchType.NotPresent, + FrostFsMatchType.StartsWith => MatchType.CommonPrefix, _ => throw new ArgumentException($"Unknown MatchType. Value: '{filter.MatchType}'.") }; diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsMatchType.cs similarity index 77% rename from src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs rename to src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsMatchType.cs index 1fdeb60..b9b3659 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectMatchType.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsMatchType.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK; -public enum FrostFsObjectMatchType +public enum FrostFsMatchType { Unspecified = 0, Equals = 1, diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs index e9552bf..feae9bd 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs @@ -2,15 +2,15 @@ namespace FrostFS.SDK; public interface IObjectFilter { - public FrostFsObjectMatchType MatchType { get; set; } + public FrostFsMatchType MatchType { get; set; } public string Key { get; set; } string? GetSerializedValue(); } -public abstract class FrostFsObjectFilter(FrostFsObjectMatchType matchType, string key, T value) : IObjectFilter +public abstract class FrostFsObjectFilter(FrostFsMatchType matchType, string key, T value) : IObjectFilter { - public FrostFsObjectMatchType MatchType { get; set; } = matchType; + public FrostFsMatchType MatchType { get; set; } = matchType; public string Key { get; set; } = key; public T Value { get; set; } = value; @@ -27,85 +27,85 @@ public abstract class FrostFsObjectFilter(FrostFsObjectMatchType matchType, s /// Match type /// Attribute key /// Attribute value -public class FilterByAttribute(FrostFsObjectMatchType matchType, string key, string value) : FrostFsObjectFilter(matchType, key, value) { } +public class FilterByAttribute(FrostFsMatchType matchType, string key, string value) : FrostFsObjectFilter(matchType, key, value) { } /// /// Creates filter to search by ObjectId /// /// Match type /// ObjectId -public class FilterByObjectId(FrostFsObjectMatchType matchType, FrostFsObjectId objectId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } +public class FilterByObjectId(FrostFsMatchType matchType, FrostFsObjectId objectId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } /// /// Creates filter to search by OwnerId /// /// Match type /// ObjectId -public class FilterByOwnerId(FrostFsObjectMatchType matchType, FrostFsOwner ownerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) { } +public class FilterByOwnerId(FrostFsMatchType matchType, FrostFsOwner ownerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) { } /// /// Creates filter to search by Version /// /// Match type /// Version -public class FilterByVersion(FrostFsObjectMatchType matchType, FrostFsVersion version) : FrostFsObjectFilter(matchType, Constants.FilterHeaderVersion, version) { } +public class FilterByVersion(FrostFsMatchType matchType, FrostFsVersion version) : FrostFsObjectFilter(matchType, Constants.FilterHeaderVersion, version) { } /// /// Creates filter to search by ContainerId /// /// Match type /// ContainerId -public class FilterByContainerId(FrostFsObjectMatchType matchType, FrostFsContainerId containerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) { } +public class FilterByContainerId(FrostFsMatchType matchType, FrostFsContainerId containerId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) { } /// /// Creates filter to search by creation Epoch /// /// Match type /// Creation Epoch -public class FilterByEpoch(FrostFsObjectMatchType matchType, ulong epoch) : FrostFsObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) { } +public class FilterByEpoch(FrostFsMatchType matchType, ulong epoch) : FrostFsObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) { } /// /// Creates filter to search by Payload Length /// /// Match type /// Payload Length -public class FilterByPayloadLength(FrostFsObjectMatchType matchType, ulong payloadLength) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { } +public class FilterByPayloadLength(FrostFsMatchType matchType, ulong payloadLength) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { } /// /// Creates filter to search by Payload Hash /// /// Match type /// Payload Hash -public class FilterByPayloadHash(FrostFsObjectMatchType matchType, CheckSum payloadHash) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { } +public class FilterByPayloadHash(FrostFsMatchType matchType, CheckSum payloadHash) : FrostFsObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { } /// /// Creates filter to search by Parent /// /// Match type /// Parent -public class FilterByParent(FrostFsObjectMatchType matchType, FrostFsObjectId parentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderParent, parentId) { } +public class FilterByParent(FrostFsMatchType matchType, FrostFsObjectId parentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderParent, parentId) { } /// /// Creates filter to search by SplitId /// /// Match type /// SplitId -public class FilterBySplitId(FrostFsObjectMatchType matchType, SplitId splitId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) { } +public class FilterBySplitId(FrostFsMatchType matchType, SplitId splitId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) { } /// /// Creates filter to search by Payload Hash /// /// Match type /// Payload Hash -public class FilterByECParent(FrostFsObjectMatchType matchType, FrostFsObjectId ecParentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) { } +public class FilterByECParent(FrostFsMatchType matchType, FrostFsObjectId ecParentId) : FrostFsObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) { } /// /// Creates filter to search Root objects /// -public class FilterByRootObject() : FrostFsObjectFilter(FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { } +public class FilterByRootObject() : FrostFsObjectFilter(FrostFsMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { } /// /// Creates filter to search objects that are physically stored on the server /// (FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { } +public class FilterByPhysicallyStored() : FrostFsObjectFilter(FrostFsMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { } diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index f344303..548d757 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -183,25 +183,25 @@ public class SmokeTests : SmokeTestsBase var networkInfo = await client.GetNetmapSnapshotAsync(); - await CheckFilter(client, containerId, new FilterByContainerId(FrostFsObjectMatchType.Equals, containerId)); + await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); - await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsObjectMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); + await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - await CheckFilter(client, containerId, new FilterBySplitId(FrostFsObjectMatchType.Equals, param.Header.Split.SplitId)); + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split.SplitId)); - await CheckFilter(client, containerId, new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test")); + await CheckFilter(client, containerId, new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test")); - await CheckFilter(client, containerId, new FilterByObjectId(FrostFsObjectMatchType.Equals, objectId)); + await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); - await CheckFilter(client, containerId, new FilterByVersion(FrostFsObjectMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - await CheckFilter(client, containerId, new FilterByEpoch(FrostFsObjectMatchType.Equals, networkInfo.Epoch)); + await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsObjectMatchType.Equals, 3)); + await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); var checkSum = CheckSum.CreateCheckSum(bytes); - await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsObjectMatchType.Equals, checkSum)); + await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); await CheckFilter(client, containerId, new FilterByPhysicallyStored()); } @@ -272,7 +272,7 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) @@ -357,7 +357,7 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) @@ -438,7 +438,7 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsObjectMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) From d7dbbf8da8aaa85a39a3884be63a2db6906e7416 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 23 Sep 2024 10:20:40 +0300 Subject: [PATCH 22/65] [#24] Client: Add Ape manager Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Client.cs | 38 +++- .../Interfaces/IFrostFSClient.cs | 10 +- .../Models/Chain/ChainTarget.cs | 37 ++++ .../Models/Chain/FrostFsChain.cs | 16 ++ .../Models/Chain/FrostFsTargetType.cs | 11 + .../Models/Containers/FrostFsContainerInfo.cs | 2 +- .../Parameters/PrmApeChainList.cs | 6 + .../Parameters/PrmApeChainRemove.cs | 8 + .../Parameters/PrmApeRemoveAdd.cs | 8 + .../Services/ContainerServiceProvider.cs | 1 - .../Services/NetmapServiceProvider.cs | 1 - .../Shared/ApeManagerServiceProvider.cs | 81 +++++++ .../Services/Shared/SessionProvider.cs | 2 - .../FrostFS.SDK.ProtosV2.csproj | 2 +- .../{apemanager => ape}/types.proto | 6 +- .../apemanager/Extension.Message.cs | 174 +++++++++++++++ .../apemanager/service.proto | 14 +- .../container/Extension.Message.cs | 168 --------------- .../container/service.proto | 203 +----------------- .../container/types.proto | 2 +- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 2 +- 21 files changed, 411 insertions(+), 381 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs rename src/FrostFS.SDK.ProtosV2/{apemanager => ape}/types.proto (86%) create mode 100644 src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/Client.cs index 6d47be5..8209571 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/Client.cs @@ -7,7 +7,6 @@ using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; using FrostFS.Session; @@ -16,6 +15,8 @@ using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Options; +using Frostfs.V2.Apemanager; +using Frostfs.V2.Ape; namespace FrostFS.SDK.ClientV2; @@ -24,8 +25,13 @@ public class Client : IFrostFSClient private bool isDisposed; internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; } + internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; } + + internal APEManagerService.APEManagerServiceClient? ApeManagerServiceClient { get; set; } + internal SessionService.SessionServiceClient? SessionServiceClient { get; set; } + internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } internal ClientEnvironment ClientCtx { get; set; } @@ -140,6 +146,26 @@ public class Client : IFrostFSClient } } + #region ApeManagerImplementation + public Task AddChainAsync(PrmApeChainAdd args) + { + var service = GetApeManagerService(args); + return service.AddChainAsync(args); + } + + public Task RemoveChainAsync(PrmApeChainRemove args) + { + var service = GetApeManagerService(args); + return service.RemoveChainAsync(args); + } + + public Task ListChainAsync(PrmApeChainList args) + { + var service = GetApeManagerService(args); + return service.ListChainAsync(args); + } + #endregion + #region ContainerImplementation public Task GetContainerAsync(PrmContainerGet args) { @@ -344,6 +370,16 @@ public class Client : IFrostFSClient return new SessionServiceProvider(client, ClientCtx); } + private ApeManagerServiceProvider GetApeManagerService(IContext ctx) + { + var callInvoker = SetupEnvironment(ctx); + var client = ApeManagerServiceClient ?? (callInvoker != null + ? new APEManagerService.APEManagerServiceClient(callInvoker) + : new APEManagerService.APEManagerServiceClient(ClientCtx.Channel)); + + return new ApeManagerServiceProvider(client, ClientCtx); + } + private ContainerServiceProvider GetContainerService(IContext ctx) { var callInvoker = SetupEnvironment(ctx); diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 80608d0..a580149 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using FrostFS.SDK.ClientV2; +using Frostfs.V2.Ape; namespace FrostFS.SDK.ClientV2.Interfaces; public interface IFrostFSClient : IDisposable @@ -19,6 +19,14 @@ public interface IFrostFSClient : IDisposable Task CreateSessionAsync(PrmSessionCreate args); #endregion + #region ApeMAnager + Task AddChainAsync(PrmApeChainAdd args); + + Task RemoveChainAsync(PrmApeChainRemove args); + + Task ListChainAsync(PrmApeChainList args); + #endregion + #region Container Task GetContainerAsync(PrmContainerGet args); diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs new file mode 100644 index 0000000..0d47993 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs @@ -0,0 +1,37 @@ +using System; + +using Frostfs.V2.Ape; + +namespace FrostFS.SDK.ClientV2 +{ + public struct FrostFsChainTarget(FrostFsTargetType type, string name) + { + private ChainTarget? chainTarget; + + public FrostFsTargetType Type { get; } = type; + + public string Name { get; } = name; + + internal ChainTarget GetChainTarget() + { + return chainTarget ??= new ChainTarget + { + Type = GetTargetType(Type), + Name = Name + }; + } + + private static TargetType GetTargetType(FrostFsTargetType type) + { + return type switch + { + FrostFsTargetType.Undefined => TargetType.Undefined, + FrostFsTargetType.Namespace => TargetType.Namespace, + FrostFsTargetType.Container => TargetType.Container, + FrostFsTargetType.User => TargetType.User, + FrostFsTargetType.Group => TargetType.Group, + _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)), + }; + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs new file mode 100644 index 0000000..0c21521 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs @@ -0,0 +1,16 @@ +using Google.Protobuf; + +namespace FrostFS.SDK.ClientV2 +{ + public struct FrostFsChain (byte[] raw) + { + private ByteString? grpcRaw; + + public byte[] Raw { get; } = raw; + + internal ByteString GetRaw() + { + return grpcRaw ??= ByteString.CopyFrom(Raw); + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs new file mode 100644 index 0000000..56617d3 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs @@ -0,0 +1,11 @@ +namespace FrostFS.SDK.ClientV2 +{ + public enum FrostFsTargetType + { + Undefined = 0, + Namespace, + Container, + User, + Group + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs index 4a4d140..74d0e41 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs @@ -12,7 +12,7 @@ namespace FrostFS.SDK; public class FrostFsContainerInfo { - private Container.Container.Types.Attribute[]? grpsAttributes; + private FrostFS.Container.Container.Types.Attribute[]? grpsAttributes; private List? attributes; private FrostFsPlacementPolicy? placementPolicy; private Guid? nonce; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs new file mode 100644 index 0000000..a68e788 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmApeChainList(FrostFsChainTarget target) : PrmBase +{ + public FrostFsChainTarget Target { get; } = target; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs new file mode 100644 index 0000000..9371b43 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain) : PrmBase +{ + public FrostFsChainTarget Target { get; } = target; + + public FrostFsChain Chain { get; } = chain; +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs new file mode 100644 index 0000000..330f601 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain) : PrmBase +{ + public FrostFsChainTarget Target { get; } = target; + + public FrostFsChain Chain { get; } = chain; +} diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index d959a5c..1e0ef9b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -7,7 +7,6 @@ using FrostFS.SDK.ClientV2; using FrostFS.Container; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2; using FrostFS.Refs; using FrostFS.Session; diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 98523ac..d932dfb 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -3,7 +3,6 @@ using System.Text; using System.Threading.Tasks; using FrostFS.Netmap; -using FrostFS.SDK.ClientV2; using static FrostFS.Netmap.NetworkConfig.Types; diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs new file mode 100644 index 0000000..596d79e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs @@ -0,0 +1,81 @@ +using System.Threading.Tasks; + +using Frostfs.V2.Ape; +using Frostfs.V2.Apemanager; + +namespace FrostFS.SDK.ClientV2; + +internal class ApeManagerServiceProvider : ContextAccessor +{ + private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient; + + internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientEnvironment context) + : base (context) + { + _apeManagerServiceClient = apeManagerServiceClient; + } + + internal async Task AddChainAsync(PrmApeChainAdd args) + { + var ctx = args.Context!; + + AddChainRequest request = new() + { + Body = new() + { + Chain = new () { Raw = args.Chain.GetRaw() }, + Target = args.Target.GetChainTarget() + } + }; + + request.AddMetaHeader(args.XHeaders); + request.Sign(ctx.Key); + + var response = await _apeManagerServiceClient!.AddChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return response.Body.ChainId.ToByteArray(); + } + + internal async Task RemoveChainAsync(PrmApeChainRemove args) + { + var ctx = args.Context!; + RemoveChainRequest request = new() + { + Body = new() + { + ChainId = args.Chain.GetRaw(), + Target = args.Target.GetChainTarget() + } + }; + + request.AddMetaHeader(args.XHeaders); + request.Sign(ctx.Key); + + var response = await _apeManagerServiceClient!.RemoveChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + } + + internal async Task ListChainAsync(PrmApeChainList args) + { + var ctx = args.Context!; + ListChainsRequest request = new() + { + Body = new() + { + Target = args.Target.GetChainTarget() + } + }; + + request.AddMetaHeader(args.XHeaders); + request.Sign(ctx.Key); + + var response = await _apeManagerServiceClient!.ListChainsAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return [.. response.Body.Chains]; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index d1cc432..9e5b142 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -1,7 +1,5 @@ using System.Threading.Tasks; -using FrostFS.SDK.ClientV2; - namespace FrostFS.SDK.ClientV2; internal interface ISessionProvider diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj index 0a28822..093351c 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj @@ -17,12 +17,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/types.proto b/src/FrostFS.SDK.ProtosV2/ape/types.proto similarity index 86% rename from src/FrostFS.SDK.ProtosV2/apemanager/types.proto rename to src/FrostFS.SDK.ProtosV2/ape/types.proto index c064627..71468c3 100644 --- a/src/FrostFS.SDK.ProtosV2/apemanager/types.proto +++ b/src/FrostFS.SDK.ProtosV2/ape/types.proto @@ -1,8 +1,8 @@ -syntax = "proto3"; +syntax = "proto3"; -package frostfs.v2.apemanager; +package frostfs.v2.ape; -option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager/grpc;apemanager"; +option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/ape/grpc;ape"; // TargetType is a type target to which a rule chain is defined. enum TargetType { diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs new file mode 100644 index 0000000..1f02ea5 --- /dev/null +++ b/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs @@ -0,0 +1,174 @@ +using Google.Protobuf; + +using FrostFS.Session; +using FrostFS.SDK.ProtosV2.Interfaces; + +namespace Frostfs.V2.Apemanager; + +public partial class AddChainRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class AddChainResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class RemoveChainRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class RemoveChainResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class ListChainsRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class ListChainsResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/service.proto b/src/FrostFS.SDK.ProtosV2/apemanager/service.proto index 6b9da60..166ba4d 100644 --- a/src/FrostFS.SDK.ProtosV2/apemanager/service.proto +++ b/src/FrostFS.SDK.ProtosV2/apemanager/service.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package frostfs.v2.apemanager; -import "apemanager/types.proto"; +import "ape/types.proto"; import "session/types.proto"; option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager/grpc;apemanager"; @@ -52,10 +52,10 @@ service APEManagerService { message AddChainRequest { message Body { // A target for which a rule chain is added. - ChainTarget target = 1; + frostfs.v2.ape.ChainTarget target = 1; // The chain to set for the target. - Chain chain = 2; + frostfs.v2.ape.Chain chain = 2; } // The request's body. @@ -95,7 +95,7 @@ message AddChainResponse { message RemoveChainRequest { message Body { // Target for which a rule chain is removed. - ChainTarget target = 1; + frostfs.v2.ape.ChainTarget target = 1; // Chain ID assigned for the rule chain. bytes chain_id = 2; @@ -135,7 +135,7 @@ message RemoveChainResponse { message ListChainsRequest { message Body { // Target for which rule chains are listed. - ChainTarget target = 1; + frostfs.v2.ape.ChainTarget target = 1; } // The request's body. @@ -154,7 +154,7 @@ message ListChainsRequest { message ListChainsResponse { message Body { // The list of chains defined for the reqeusted target. - repeated Chain chains = 1; + repeated frostfs.v2.ape.Chain chains = 1; } // The response's body. @@ -168,4 +168,4 @@ message ListChainsResponse { // authenticate the nodes of the message route and check the correctness of // transmission. neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index 5d48399..0b876f0 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -5,62 +5,6 @@ using FrostFS.SDK.ProtosV2.Interfaces; namespace FrostFS.Container; -public partial class AnnounceUsedSpaceRequest : IRequest -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (RequestMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (RequestVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} - -public partial class AnnounceUsedSpaceResponse : IResponse -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (ResponseMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (ResponseVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} - public partial class GetRequest : IRequest { IMetaHeader IVerifiableMessage.GetMetaHeader() @@ -284,115 +228,3 @@ public partial class ListResponse : IResponse return Body; } } - -public partial class SetExtendedACLRequest : IRequest -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (RequestMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (RequestVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} - -public partial class SetExtendedACLResponse : IResponse -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (ResponseMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (ResponseVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} - -public partial class GetExtendedACLRequest : IRequest -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (RequestMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (RequestVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} - -public partial class GetExtendedACLResponse : IResponse -{ - IMetaHeader IVerifiableMessage.GetMetaHeader() - { - return MetaHeader; - } - - IVerificationHeader IVerifiableMessage.GetVerificationHeader() - { - return VerifyHeader; - } - - void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) - { - MetaHeader = (ResponseMetaHeader)metaHeader; - } - - void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) - { - VerifyHeader = (ResponseVerificationHeader)verificationHeader; - } - - public IMessage GetBody() - { - return Body; - } -} diff --git a/src/FrostFS.SDK.ProtosV2/container/service.proto b/src/FrostFS.SDK.ProtosV2/container/service.proto index 4a5cc02..abf3e9d 100644 --- a/src/FrostFS.SDK.ProtosV2/container/service.proto +++ b/src/FrostFS.SDK.ProtosV2/container/service.proto @@ -11,8 +11,8 @@ import "refs/types.proto"; import "session/types.proto"; // `ContainerService` provides API to interact with `Container` smart contract -// in NeoFS sidechain via other NeoFS nodes. All of those actions can be done -// equivalently by directly issuing transactions and RPC calls to sidechain +// in FrostFS sidechain via other FrostFS nodes. All of those actions can be +// done equivalently by directly issuing transactions and RPC calls to sidechain // nodes. service ContainerService { // `Put` invokes `Container` smart contract's `Put` method and returns @@ -25,7 +25,7 @@ service ContainerService { // request to save the container has been sent to the sidechain; // - Common failures (SECTION_FAILURE_COMMON); // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // container create access denied. + // container create access denied. rpc Put(PutRequest) returns (PutResponse); // `Delete` invokes `Container` smart contract's `Delete` method and returns @@ -38,7 +38,7 @@ service ContainerService { // request to remove the container has been sent to the sidechain; // - Common failures (SECTION_FAILURE_COMMON); // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // container delete access denied. + // container delete access denied. rpc Delete(DeleteRequest) returns (DeleteResponse); // Returns container structure from `Container` smart contract storage. @@ -50,7 +50,7 @@ service ContainerService { // - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \ // requested container not found; // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // access to container is denied. + // access to container is denied. rpc Get(GetRequest) returns (GetResponse); // Returns all owner's containers from 'Container` smart contract' storage. @@ -60,47 +60,11 @@ service ContainerService { // container list has been successfully read; // - Common failures (SECTION_FAILURE_COMMON); // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // container list access denied. + // container list access denied. rpc List(ListRequest) returns (ListResponse); - - // Invokes 'SetEACL' method of 'Container` smart contract and returns response - // immediately. After one more block in sidechain, changes in an Extended ACL - // are added into smart contract storage. - // - // Statuses: - // - **OK** (0, SECTION_SUCCESS): \ - // request to save container eACL has been sent to the sidechain; - // - Common failures (SECTION_FAILURE_COMMON); - // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // set container eACL access denied. - rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse); - - // Returns Extended ACL table and signature from `Container` smart contract - // storage. - // - // Statuses: - // - **OK** (0, SECTION_SUCCESS): \ - // container eACL has been successfully read; - // - Common failures (SECTION_FAILURE_COMMON); - // - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \ - // container not found; - // - **EACL_NOT_FOUND** (3073, SECTION_CONTAINER): \ - // eACL table not found; - // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ - // access to container eACL is denied. - rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse); - - // Announces the space values used by the container for P2P synchronization. - // - // Statuses: - // - **OK** (0, SECTION_SUCCESS): \ - // estimation of used space has been successfully announced; - // - Common failures (SECTION_FAILURE_COMMON). - rpc AnnounceUsedSpace(AnnounceUsedSpaceRequest) - returns (AnnounceUsedSpaceResponse); } -// New NeoFS Container creation request +// New FrostFS Container creation request message PutRequest { // Container creation request has container structure's signature as a // separate field. It's not stored in sidechain, just verified on container @@ -108,7 +72,7 @@ message PutRequest { // the stable-marshalled container strucutre, hence there is no need for // additional signature checks. message Body { - // Container structure to register in NeoFS + // Container structure to register in FrostFS container.Container container = 1; // Signature of a stable-marshalled container according to RFC-6979. @@ -127,7 +91,7 @@ message PutRequest { neo.fs.v2.session.RequestVerificationHeader verify_header = 3; } -// New NeoFS Container creation response +// New FrostFS Container creation response message PutResponse { // Container put response body contains information about the newly registered // container as seen by `Container` smart contract. `ContainerID` can be @@ -156,7 +120,7 @@ message DeleteRequest { // the container owner's intent. The signature will be verified by `Container` // smart contract, so signing algorithm must be supported by NeoVM. message Body { - // Identifier of the container to delete from NeoFS + // Identifier of the container to delete from FrostFS neo.fs.v2.refs.ContainerID container_id = 1; // `ContainerID` signed with the container owner's key according to @@ -282,150 +246,3 @@ message ListResponse { // transmission. neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; } - -// Set Extended ACL -message SetExtendedACLRequest { - // Set Extended ACL request body does not have separate `ContainerID` - // reference. It will be taken from `EACLTable.container_id` field. - message Body { - // Extended ACL table to set for the container - neo.fs.v2.acl.EACLTable eacl = 1; - - // Signature of stable-marshalled Extended ACL table according to RFC-6979. - neo.fs.v2.refs.SignatureRFC6979 signature = 2; - } - // Body of set extended acl request message. - Body body = 1; - - // Carries request meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.RequestMetaHeader meta_header = 2; - - // Carries request verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.RequestVerificationHeader verify_header = 3; -} - -// Set Extended ACL -message SetExtendedACLResponse { - // `SetExtendedACLResponse` has an empty body because the operation is - // asynchronous and the update should be reflected in `Container` smart - // contract's storage after next block is issued in sidechain. - message Body {} - - // Body of set extended acl response message. - Body body = 1; - - // Carries response meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.ResponseMetaHeader meta_header = 2; - - // Carries response verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; -} - -// Get Extended ACL -message GetExtendedACLRequest { - // Get Extended ACL request body - message Body { - // Identifier of the container having Extended ACL - neo.fs.v2.refs.ContainerID container_id = 1; - } - - // Body of get extended acl request message. - Body body = 1; - - // Carries request meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.RequestMetaHeader meta_header = 2; - - // Carries request verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.RequestVerificationHeader verify_header = 3; -} - -// Get Extended ACL -message GetExtendedACLResponse { - // Get Extended ACL Response body can be empty if the requested container does - // not have Extended ACL Table attached or Extended ACL has not been allowed - // at the time of container creation. - message Body { - // Extended ACL requested, if available - neo.fs.v2.acl.EACLTable eacl = 1; - - // Signature of stable-marshalled Extended ACL according to RFC-6979. - neo.fs.v2.refs.SignatureRFC6979 signature = 2; - - // Session token if Extended ACL was set within a session - neo.fs.v2.session.SessionToken session_token = 3; - } - // Body of get extended acl response message. - Body body = 1; - - // Carries response meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.ResponseMetaHeader meta_header = 2; - - // Carries response verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; -} - -// Announce container used space -message AnnounceUsedSpaceRequest { - // Container used space announcement body. - message Body { - // Announcement contains used space information for a single container. - message Announcement { - // Epoch number for which the container size estimation was produced. - uint64 epoch = 1; - - // Identifier of the container. - neo.fs.v2.refs.ContainerID container_id = 2; - - // Used space is a sum of object payload sizes of a specified - // container, stored in the node. It must not include inhumed objects. - uint64 used_space = 3; - } - - // List of announcements. If nodes share several containers, - // announcements are transferred in a batch. - repeated Announcement announcements = 1; - } - - // Body of announce used space request message. - Body body = 1; - - // Carries request meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.RequestMetaHeader meta_header = 2; - - // Carries request verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.RequestVerificationHeader verify_header = 3; -} - -// Announce container used space -message AnnounceUsedSpaceResponse { - // `AnnounceUsedSpaceResponse` has an empty body because announcements are - // one way communication. - message Body {} - - // Body of announce used space response message. - Body body = 1; - - // Carries response meta information. Header data is used only to regulate - // message transport and does not affect request execution. - neo.fs.v2.session.ResponseMetaHeader meta_header = 2; - - // Carries response verification information. This header is used to - // authenticate the nodes of the message route and check the correctness of - // transmission. - neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; -} diff --git a/src/FrostFS.SDK.ProtosV2/container/types.proto b/src/FrostFS.SDK.ProtosV2/container/types.proto index 075081a..c657d1c 100644 --- a/src/FrostFS.SDK.ProtosV2/container/types.proto +++ b/src/FrostFS.SDK.ProtosV2/container/types.proto @@ -50,7 +50,7 @@ message Container { // (`__NEOFS__DISABLE_HOMOMORPHIC_HASHING` is deprecated) \ // Disables homomorphic hashing for the container if the value equals "true" // string. Any other values are interpreted as missing attribute. Container - // could be accepted in a NeoFS network only if the global network hashing + // could be accepted in a FrostFS network only if the global network hashing // configuration value corresponds with that attribute's value. After // container inclusion, network setting is ignored. // diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index 60e4a20..45f3460 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -7,7 +7,7 @@ namespace FrostFS.SDK.SmokeTests; public abstract class SmokeTestsBase { - protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + protected readonly string key = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; protected readonly string url = "http://172.23.32.4:8080"; protected ECDsa Key { get; } From d1271df207e4811f26902820e0f2099185b4b18b Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 23 Sep 2024 18:53:21 +0300 Subject: [PATCH 23/65] [#13] Client: Use code analyzers Signed-off-by: Pavel Gross --- .editorconfig | 901 ++++++++++++++++++ FrostFS.SDK.sln | 12 +- .../Exceptions/FrostFsException.cs | 18 + .../Exceptions/InvalidObjectException.cs | 13 +- .../Exceptions/ResponseException.cs | 21 +- .../FrostFS.SDK.ClientV2.csproj | 15 +- .../{Client.cs => FrostFSClient.cs} | 129 ++- .../Interceptors/MetricsInterceptor.cs | 26 +- .../Interfaces/IFrostFSClient.cs | 6 +- src/FrostFS.SDK.ClientV2/Mappers/Container.cs | 10 +- .../Mappers/ContainerId.cs | 16 +- .../Mappers/MetaHeader.cs | 9 +- .../Mappers/Netmap/Netmap.cs | 6 + .../Mappers/Netmap/NodeInfo.cs | 10 + .../Mappers/Netmap/PlacementPolicy.cs | 24 +- .../Mappers/Netmap/Replica.cs | 7 + .../Mappers/Object/Object.cs | 2 +- .../Mappers/Object/ObjectAttributeMapper.cs | 18 +- .../Mappers/Object/ObjectFilterMapper.cs | 7 +- .../Mappers/Object/ObjectHeaderMapper.cs | 26 +- .../Mappers/Object/ObjectId.cs | 12 + src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs | 12 +- .../Mappers/Session/SessionMapper.cs | 9 +- .../Mappers/SignatureMapper.cs | 8 +- src/FrostFS.SDK.ClientV2/Mappers/Status.cs | 2 +- src/FrostFS.SDK.ClientV2/Mappers/Version.cs | 12 +- .../Models/Chain/ChainTarget.cs | 28 +- .../Models/Chain/FrostFsChain.cs | 28 +- .../Models/Chain/FrostFsTargetType.cs | 2 +- .../Models/Client/ClientSettings.cs | 27 +- .../Models/Containers/FrostFsContainerId.cs | 28 +- .../Models/Containers/FrostFsContainerInfo.cs | 46 +- .../Models/Enums/BasicAcl.cs | 21 - .../Models/Misc/CheckSum.cs | 11 +- .../Models/Misc/Constants.cs | 4 +- .../Models/Netmap/FrostFsPlacementPolicy.cs | 79 +- .../Models/Netmap/FrostFsReplica.cs | 34 +- .../Models/Netmap/FrostFsVersion.cs | 7 +- ...FsAttribute.cs => FrostFsAttributePair.cs} | 2 +- .../Models/Object/FrostFsLinkObject.cs | 20 +- .../Models/Object/FrostFsObject.cs | 28 +- .../Models/Object/FrostFsObjectFilter.cs | 2 +- .../Models/Object/FrostFsObjectHeader.cs | 19 +- .../Models/Object/FrostFsObjectId.cs | 5 + .../Models/Object/FrostFsOwner.cs | 6 +- .../Models/Object/FrostFsSplit.cs | 20 +- .../Models/Object/SplitId.cs | 26 +- .../Models/Response/FrostFsSignature.cs | 2 +- .../Parameters/Context.cs | 24 +- .../Parameters/PrmBase.cs | 4 +- .../Parameters/PrmContainerCreate.cs | 2 +- .../Parameters/PrmNetmapSnapshot.cs | 2 +- .../Parameters/PrmNetworkSettings.cs | 2 +- .../Parameters/PrmNodeInfo.cs | 2 +- .../Parameters/PrmObjectHeadGet.cs | 2 +- .../Parameters/PrmObjectPut.cs | 6 +- .../Services/ContainerServiceProvider.cs | 77 +- .../Services/NetmapServiceProvider.cs | 43 +- .../Services/ObjectServiceProvider.cs | 183 ++-- .../Services/SessionServiceProvider.cs | 21 +- .../Shared/ApeManagerServiceProvider.cs | 22 +- .../Services/Shared/SessionProvider.cs | 9 +- .../Tools/ClientEnvironment.cs | 8 +- src/FrostFS.SDK.ClientV2/Tools/Object.cs | 57 -- .../Tools/ObjectReader.cs | 24 +- .../Tools/ObjectStreamer.cs | 14 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 39 +- src/FrostFS.SDK.ClientV2/Tools/Range.cs | 18 +- .../Tools/RequestConstructor.cs | 28 +- .../Tools/RequestSigner.cs | 42 +- .../Tools/SearchReader.cs | 8 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 46 +- src/FrostFS.SDK.ClientV2/Ростелеком.txt | 8 + src/FrostFS.SDK.Cryptography/Base58.cs | 15 +- src/FrostFS.SDK.Cryptography/Extentions.cs | 12 +- .../FrostFS.SDK.Cryptography.csproj | 4 + src/FrostFS.SDK.Cryptography/Key.cs | 28 +- src/FrostFS.SDK.Cryptography/Murmur3_128.cs | 6 +- src/FrostFS.SDK.Cryptography/Range.cs | 6 +- src/FrostFS.SDK.Cryptography/UUID.cs | 12 +- .../Interfaces/IVerificationHeader.cs | 1 + .../apemanager/Extension.Message.cs | 6 +- .../container/Extension.Message.cs | 6 +- .../netmap/Extension.Message.cs | 1 + .../object/Extension.Message.cs | 4 +- .../session/Extension.Message.cs | 1 + src/FrostFS.SDK.Tests/ContainerTest.cs | 27 +- src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 12 +- .../Mocks/AsyncStreamReaderMock.cs | 2 +- .../Mocks/ClientStreamWriter.cs | 7 +- .../ContainerServiceBase.cs | 34 +- .../ContainerServiceMocks/ContainerStub.cs | 1 + .../ContainerServiceMocks/GetContainerMock.cs | 32 +- .../ContainerServiceMocks/RequestData.cs | 1 - src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 7 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 20 +- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 3 + src/FrostFS.SDK.Tests/NetworkTest.cs | 28 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 46 +- src/FrostFS.SDK.Tests/SessionTests.cs | 12 +- src/FrostFS.SDK.Tests/SmokeTests.cs | 99 +- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 1 + 102 files changed, 2168 insertions(+), 733 deletions(-) create mode 100644 .editorconfig create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs rename src/FrostFS.SDK.ClientV2/{Client.cs => FrostFSClient.cs} (79%) delete mode 100644 src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs rename src/FrostFS.SDK.ClientV2/Models/Object/{FrostFsAttribute.cs => FrostFsAttributePair.cs} (66%) delete mode 100644 src/FrostFS.SDK.ClientV2/Tools/Object.cs create mode 100644 src/FrostFS.SDK.ClientV2/Ростелеком.txt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7a29e50 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,901 @@ +[*.cs] + +# CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = warning + +# CA1000: Do not declare static members on generic types +dotnet_diagnostic.CA1000.severity = warning + +# CA1002: Do not expose generic lists +dotnet_diagnostic.CA1002.severity = warning + +# CA1003: Use generic event handler instances +dotnet_diagnostic.CA1003.severity = warning + +# CA1005: Avoid excessive parameters on generic types +dotnet_diagnostic.CA1005.severity = warning + +# CA1008: Enums should have zero value +dotnet_diagnostic.CA1008.severity = warning + +# CA1010: Generic interface should also be implemented +dotnet_diagnostic.CA1010.severity = warning + +# CA1012: Abstract types should not have public constructors +dotnet_diagnostic.CA1012.severity = warning + +# CA1014: Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = warning + +# CA1016: Mark assemblies with assembly version +dotnet_diagnostic.CA1016.severity = warning + +# CA1017: Mark assemblies with ComVisible +dotnet_diagnostic.CA1017.severity = warning + +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = warning + +# CA1019: Define accessors for attribute arguments +dotnet_diagnostic.CA1019.severity = warning + +# CA1021: Avoid out parameters +dotnet_diagnostic.CA1021.severity = warning + +# CA1024: Use properties where appropriate +dotnet_diagnostic.CA1024.severity = warning + +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = warning + +# CA1028: Enum Storage should be Int32 +dotnet_diagnostic.CA1028.severity = warning + +# CA1030: Use events where appropriate +dotnet_diagnostic.CA1030.severity = warning + +# CA1031: Do not catch general exception types +dotnet_diagnostic.CA1031.severity = warning + +# CA1033: Interface methods should be callable by child types +dotnet_diagnostic.CA1033.severity = warning + +# CA1034: Nested types should not be visible +dotnet_diagnostic.CA1034.severity = warning + +# CA1036: Override methods on comparable types +dotnet_diagnostic.CA1036.severity = warning + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = warning + +# CA1041: Provide ObsoleteAttribute message +dotnet_diagnostic.CA1041.severity = warning + +# CA1043: Use Integral Or String Argument For Indexers +dotnet_diagnostic.CA1043.severity = warning + +# CA1044: Properties should not be write only +dotnet_diagnostic.CA1044.severity = warning + +# CA1045: Do not pass types by reference +dotnet_diagnostic.CA1045.severity = warning + +# CA1046: Do not overload equality operator on reference types +dotnet_diagnostic.CA1046.severity = warning + +# CA1050: Declare types in namespaces +dotnet_diagnostic.CA1050.severity = warning + +# CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = warning + +# CA1052: Static holder types should be Static or NotInheritable +dotnet_diagnostic.CA1052.severity = warning + +# CA1054: URI-like parameters should not be strings +dotnet_diagnostic.CA1054.severity = warning + +# CA1055: URI-like return values should not be strings +dotnet_diagnostic.CA1055.severity = warning + +# CA1056: URI-like properties should not be strings +dotnet_diagnostic.CA1056.severity = warning + +# CA1058: Types should not extend certain base types +dotnet_diagnostic.CA1058.severity = warning + +# CA1060: Move pinvokes to native methods class +dotnet_diagnostic.CA1060.severity = warning + +# CA1061: Do not hide base class methods +dotnet_diagnostic.CA1061.severity = warning + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = warning + +# CA1063: Implement IDisposable Correctly +dotnet_diagnostic.CA1063.severity = warning + +# CA1064: Exceptions should be public +dotnet_diagnostic.CA1064.severity = warning + +# CA1065: Do not raise exceptions in unexpected locations +dotnet_diagnostic.CA1065.severity = warning + +# CA1066: Implement IEquatable when overriding Object.Equals +dotnet_diagnostic.CA1066.severity = warning + +# CA1067: Override Object.Equals(object) when implementing IEquatable +dotnet_diagnostic.CA1067.severity = warning + +# CA1068: CancellationToken parameters must come last +dotnet_diagnostic.CA1068.severity = warning + +# CA1069: Enums values should not be duplicated +dotnet_diagnostic.CA1069.severity = warning + +# CA1070: Do not declare event fields as virtual +dotnet_diagnostic.CA1070.severity = warning + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = warning + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = warning + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = warning + +# CA1307: Specify StringComparison for clarity +dotnet_diagnostic.CA1307.severity = warning + +# CA1308: Normalize strings to uppercase +dotnet_diagnostic.CA1308.severity = warning + +# CA1310: Specify StringComparison for correctness +dotnet_diagnostic.CA1310.severity = warning + +# CA1401: P/Invokes should not be visible +dotnet_diagnostic.CA1401.severity = warning + +# CA1416: Validate platform compatibility +dotnet_diagnostic.CA1416.severity = warning + +# CA1417: Do not use 'OutAttribute' on string parameters for P/Invokes +dotnet_diagnostic.CA1417.severity = warning + +# CA1418: Use valid platform string +dotnet_diagnostic.CA1418.severity = warning + +# CA1419: Provide a parameterless constructor that is as visible as the containing type for concrete types derived from 'System.Runtime.InteropServices.SafeHandle' +dotnet_diagnostic.CA1419.severity = warning + +# CA1420: Property, type, or attribute requires runtime marshalling +dotnet_diagnostic.CA1420.severity = warning + +# CA1421: This method uses runtime marshalling even when the 'DisableRuntimeMarshallingAttribute' is applied +dotnet_diagnostic.CA1421.severity = warning + +# CA1422: Validate platform compatibility +dotnet_diagnostic.CA1422.severity = warning + +# CA1501: Avoid excessive inheritance +dotnet_diagnostic.CA1501.severity = warning + +# CA1502: Avoid excessive complexity +dotnet_diagnostic.CA1502.severity = warning + +# CA1505: Avoid unmaintainable code +dotnet_diagnostic.CA1505.severity = warning + +# CA1506: Avoid excessive class coupling +# dotnet_diagnostic.CA1506.severity = warning + +# CA1509: Invalid entry in code metrics rule specification file +dotnet_diagnostic.CA1509.severity = warning + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = warning + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = warning + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = warning + +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = warning + +# CA1700: Do not name enum values 'Reserved' +dotnet_diagnostic.CA1700.severity = warning + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = warning + +# CA1708: Identifiers should differ by more than case +dotnet_diagnostic.CA1708.severity = warning + +# CA1710: Identifiers should have correct suffix +dotnet_diagnostic.CA1710.severity = warning + +# CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1711.severity = warning + +# CA1712: Do not prefix enum values with type name +dotnet_diagnostic.CA1712.severity = warning + +# CA1713: Events should not have 'Before' or 'After' prefix +dotnet_diagnostic.CA1713.severity = warning + +# CA1715: Identifiers should have correct prefix +dotnet_diagnostic.CA1715.severity = warning + +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.CA1716.severity = warning + +# CA1720: Identifier contains type name +dotnet_diagnostic.CA1720.severity = warning + +# CA1721: Property names should not match get methods +dotnet_diagnostic.CA1721.severity = warning + +# CA1724: Type names should not match namespaces +dotnet_diagnostic.CA1724.severity = warning + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = warning + +# CA1727: Use PascalCase for named placeholders +dotnet_diagnostic.CA1727.severity = warning + +# CA1806: Do not ignore method results +dotnet_diagnostic.CA1806.severity = warning + +# CA1810: Initialize reference type static fields inline +dotnet_diagnostic.CA1810.severity = warning + +# CA1813: Avoid unsealed attributes +dotnet_diagnostic.CA1813.severity = warning + +# CA1814: Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1814.severity = warning + +# CA1815: Override equals and operator equals on value types +dotnet_diagnostic.CA1815.severity = warning + +# CA1816: Dispose methods should call SuppressFinalize +dotnet_diagnostic.CA1816.severity = warning + +# CA1819: Properties should not return arrays +# dotnet_diagnostic.CA1819.severity = warning + +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = warning + +# CA1821: Remove empty Finalizers +dotnet_diagnostic.CA1821.severity = warning + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = warning + +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning + +# CA1826: Do not use Enumerable methods on indexable collections +dotnet_diagnostic.CA1826.severity = warning + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = warning + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = warning + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = warning + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1830.severity = warning + +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = warning + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = warning + +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = warning + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = warning + +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = warning + +# CA1836: Prefer IsEmpty over Count +dotnet_diagnostic.CA1836.severity = warning + +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = warning + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = warning + +# CA1839: Use 'Environment.ProcessPath' +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +dotnet_diagnostic.CA1840.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = warning + +# CA1846: Prefer 'AsSpan' over 'Substring' +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use char literal for a single character lookup +dotnet_diagnostic.CA1847.severity = warning + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = warning + +# CA1849: Call async methods when in an async method +dotnet_diagnostic.CA1849.severity = warning + +# CA1850: Prefer static 'HashData' method over 'ComputeHash' +dotnet_diagnostic.CA1850.severity = warning + +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = warning + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +dotnet_diagnostic.CA1853.severity = warning + +# CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1854.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = warning + +# CA1859: Use concrete types when possible for improved performance +dotnet_diagnostic.CA1859.severity = warning + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = warning + +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = warning + +# CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +dotnet_diagnostic.CA1862.severity = warning + +# CA1863: Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = warning + +# CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method +dotnet_diagnostic.CA1864.severity = warning + +# CA1868: Unnecessary call to 'Contains(item)' +dotnet_diagnostic.CA1868.severity = warning + +# CA1869: Cache and reuse 'JsonSerializerOptions' instances +dotnet_diagnostic.CA1869.severity = warning + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = warning + +# CA2002: Do not lock on objects with weak identity +dotnet_diagnostic.CA2002.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = warning + +# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA2009.severity = warning + +# CA2011: Avoid infinite recursion +dotnet_diagnostic.CA2011.severity = warning + +# CA2012: Use ValueTasks correctly +dotnet_diagnostic.CA2012.severity = warning + +# CA2013: Do not use ReferenceEquals with value types +dotnet_diagnostic.CA2013.severity = warning + +# CA2015: Do not define finalizers for types derived from MemoryManager +dotnet_diagnostic.CA2015.severity = warning + +# CA2017: Parameter count mismatch +dotnet_diagnostic.CA2017.severity = warning + +# CA2018: 'Buffer.BlockCopy' expects the number of bytes to be copied for the 'count' argument +dotnet_diagnostic.CA2018.severity = warning + +# CA2019: Improper 'ThreadStatic' field initialization +dotnet_diagnostic.CA2019.severity = warning + +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +dotnet_diagnostic.CA2021.severity = warning + +# CA2100: Review SQL queries for security vulnerabilities +dotnet_diagnostic.CA2100.severity = warning + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = warning + +# CA2119: Seal methods that satisfy private interfaces +dotnet_diagnostic.CA2119.severity = warning + +# CA2153: Do Not Catch Corrupted State Exceptions +dotnet_diagnostic.CA2153.severity = warning + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2207: Initialize value type static fields inline +dotnet_diagnostic.CA2207.severity = warning + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# CA2211: Non-constant fields should not be visible +dotnet_diagnostic.CA2211.severity = warning + +# CA2213: Disposable fields should be disposed +dotnet_diagnostic.CA2213.severity = warning + +# CA2214: Do not call overridable methods in constructors +dotnet_diagnostic.CA2214.severity = warning + +# CA2215: Dispose methods should call base class dispose +dotnet_diagnostic.CA2215.severity = warning + +# CA2216: Disposable types should declare finalizer +dotnet_diagnostic.CA2216.severity = warning + +# CA2217: Do not mark enums with FlagsAttribute +dotnet_diagnostic.CA2217.severity = warning + +# CA2219: Do not raise exceptions in finally clauses +dotnet_diagnostic.CA2219.severity = warning + +# CA2225: Operator overloads have named alternates +dotnet_diagnostic.CA2225.severity = warning + +# CA2226: Operators should have symmetrical overloads +dotnet_diagnostic.CA2226.severity = warning + +# CA2227: Collection properties should be read only +dotnet_diagnostic.CA2227.severity = warning + +# CA2231: Overload operator equals on overriding value type Equals +dotnet_diagnostic.CA2231.severity = warning + +# CA2235: Mark all non-serializable fields +dotnet_diagnostic.CA2235.severity = warning + +# CA2237: Mark ISerializable types with serializable +dotnet_diagnostic.CA2237.severity = warning + +# CA2241: Provide correct arguments to formatting methods +dotnet_diagnostic.CA2241.severity = warning + +# CA2242: Test for NaN correctly +dotnet_diagnostic.CA2242.severity = warning + +# CA2243: Attribute string literals should parse correctly +dotnet_diagnostic.CA2243.severity = warning + +# CA2244: Do not duplicate indexed element initializations +dotnet_diagnostic.CA2244.severity = warning + +# CA2245: Do not assign a property to itself +dotnet_diagnostic.CA2245.severity = warning + +# CA2246: Assigning symbol and its member in the same statement +dotnet_diagnostic.CA2246.severity = warning + +# CA2247: Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum +dotnet_diagnostic.CA2247.severity = warning + +# CA2248: Provide correct 'enum' argument to 'Enum.HasFlag' +dotnet_diagnostic.CA2248.severity = warning + +# CA2249: Consider using 'string.Contains' instead of 'string.IndexOf' +dotnet_diagnostic.CA2249.severity = warning + +# CA2250: Use 'ThrowIfCancellationRequested' +dotnet_diagnostic.CA2250.severity = warning + +# CA2251: Use 'string.Equals' +dotnet_diagnostic.CA2251.severity = warning + +# CA2253: Named placeholders should not be numeric values +dotnet_diagnostic.CA2253.severity = warning + +# CA2254: Template should be a static expression +dotnet_diagnostic.CA2254.severity = warning + +# CA2255: The 'ModuleInitializer' attribute should not be used in libraries +dotnet_diagnostic.CA2255.severity = warning + +# CA2256: All members declared in parent interfaces must have an implementation in a DynamicInterfaceCastableImplementation-attributed interface +dotnet_diagnostic.CA2256.severity = warning + +# CA2257: Members defined on an interface with the 'DynamicInterfaceCastableImplementationAttribute' should be 'static' +dotnet_diagnostic.CA2257.severity = warning + +# CA2258: Providing a 'DynamicInterfaceCastableImplementation' interface in Visual Basic is unsupported +dotnet_diagnostic.CA2258.severity = warning + +# CA2259: 'ThreadStatic' only affects static fields +dotnet_diagnostic.CA2259.severity = warning + +# CA2261: Do not use ConfigureAwaitOptions.SuppressThrowing with Task +dotnet_diagnostic.CA2261.severity = warning + +# CA2300: Do not use insecure deserializer BinaryFormatter +dotnet_diagnostic.CA2300.severity = warning + +# CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder +dotnet_diagnostic.CA2301.severity = warning + +# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +dotnet_diagnostic.CA2302.severity = warning + +# CA2305: Do not use insecure deserializer LosFormatter +dotnet_diagnostic.CA2305.severity = warning + +# CA2310: Do not use insecure deserializer NetDataContractSerializer +dotnet_diagnostic.CA2310.severity = warning + +# CA2311: Do not deserialize without first setting NetDataContractSerializer.Binder +dotnet_diagnostic.CA2311.severity = warning + +# CA2312: Ensure NetDataContractSerializer.Binder is set before deserializing +dotnet_diagnostic.CA2312.severity = warning + +# CA2315: Do not use insecure deserializer ObjectStateFormatter +dotnet_diagnostic.CA2315.severity = warning + +# CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver +dotnet_diagnostic.CA2321.severity = warning + +# CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing +dotnet_diagnostic.CA2322.severity = warning + +# CA2326: Do not use TypeNameHandling values other than None +dotnet_diagnostic.CA2326.severity = warning + +# CA2327: Do not use insecure JsonSerializerSettings +dotnet_diagnostic.CA2327.severity = warning + +# CA2328: Ensure that JsonSerializerSettings are secure +dotnet_diagnostic.CA2328.severity = warning + +# CA2329: Do not deserialize with JsonSerializer using an insecure configuration +dotnet_diagnostic.CA2329.severity = warning + +# CA2330: Ensure that JsonSerializer has a secure configuration when deserializing +dotnet_diagnostic.CA2330.severity = warning + +# CA2350: Do not use DataTable.ReadXml() with untrusted data +dotnet_diagnostic.CA2350.severity = warning + +# CA2351: Do not use DataSet.ReadXml() with untrusted data +dotnet_diagnostic.CA2351.severity = warning + +# CA2361: Ensure auto-generated class containing DataSet.ReadXml() is not used with untrusted data +dotnet_diagnostic.CA2361.severity = warning + +# CA3001: Review code for SQL injection vulnerabilities +dotnet_diagnostic.CA3001.severity = warning + +# CA3002: Review code for XSS vulnerabilities +dotnet_diagnostic.CA3002.severity = warning + +# CA3003: Review code for file path injection vulnerabilities +dotnet_diagnostic.CA3003.severity = warning + +# CA3004: Review code for information disclosure vulnerabilities +dotnet_diagnostic.CA3004.severity = warning + +# CA3005: Review code for LDAP injection vulnerabilities +dotnet_diagnostic.CA3005.severity = warning + +# CA3006: Review code for process command injection vulnerabilities +dotnet_diagnostic.CA3006.severity = warning + +# CA3007: Review code for open redirect vulnerabilities +dotnet_diagnostic.CA3007.severity = warning + +# CA3008: Review code for XPath injection vulnerabilities +dotnet_diagnostic.CA3008.severity = warning + +# CA3009: Review code for XML injection vulnerabilities +dotnet_diagnostic.CA3009.severity = warning + +# CA3010: Review code for XAML injection vulnerabilities +dotnet_diagnostic.CA3010.severity = warning + +# CA3011: Review code for DLL injection vulnerabilities +dotnet_diagnostic.CA3011.severity = warning + +# CA3012: Review code for regex injection vulnerabilities +dotnet_diagnostic.CA3012.severity = warning + +# CA3061: Do Not Add Schema By URL +dotnet_diagnostic.CA3061.severity = warning + +# CA3075: Insecure DTD processing in XML +dotnet_diagnostic.CA3075.severity = warning + +# CA3076: Insecure XSLT script processing +dotnet_diagnostic.CA3076.severity = warning + +# CA3077: Insecure Processing in API Design, XmlDocument and XmlTextReader +dotnet_diagnostic.CA3077.severity = warning + +# CA3147: Mark Verb Handlers With Validate Antiforgery Token +dotnet_diagnostic.CA3147.severity = warning + +# CA5350: Do Not Use Weak Cryptographic Algorithms +dotnet_diagnostic.CA5350.severity = warning + +# CA5351: Do Not Use Broken Cryptographic Algorithms +dotnet_diagnostic.CA5351.severity = warning + +# CA5358: Review cipher mode usage with cryptography experts +dotnet_diagnostic.CA5358.severity = warning + +# CA5359: Do Not Disable Certificate Validation +dotnet_diagnostic.CA5359.severity = warning + +# CA5360: Do Not Call Dangerous Methods In Deserialization +dotnet_diagnostic.CA5360.severity = warning + +# CA5361: Do Not Disable SChannel Use of Strong Crypto +dotnet_diagnostic.CA5361.severity = warning + +# CA5362: Potential reference cycle in deserialized object graph +dotnet_diagnostic.CA5362.severity = warning + +# CA5363: Do Not Disable Request Validation +dotnet_diagnostic.CA5363.severity = warning + +# CA5364: Do Not Use Deprecated Security Protocols +dotnet_diagnostic.CA5364.severity = warning + +# CA5365: Do Not Disable HTTP Header Checking +dotnet_diagnostic.CA5365.severity = warning + +# CA5366: Use XmlReader for 'DataSet.ReadXml()' +dotnet_diagnostic.CA5366.severity = warning + +# CA5367: Do Not Serialize Types With Pointer Fields +dotnet_diagnostic.CA5367.severity = warning + +# CA5368: Set ViewStateUserKey For Classes Derived From Page +dotnet_diagnostic.CA5368.severity = warning + +# CA5369: Use XmlReader for 'XmlSerializer.Deserialize()' +dotnet_diagnostic.CA5369.severity = warning + +# CA5370: Use XmlReader for XmlValidatingReader constructor +dotnet_diagnostic.CA5370.severity = warning + +# CA5371: Use XmlReader for 'XmlSchema.Read()' +dotnet_diagnostic.CA5371.severity = warning + +# CA5372: Use XmlReader for XPathDocument constructor +dotnet_diagnostic.CA5372.severity = warning + +# CA5373: Do not use obsolete key derivation function +dotnet_diagnostic.CA5373.severity = warning + +# CA5374: Do Not Use XslTransform +dotnet_diagnostic.CA5374.severity = warning + +# CA5375: Do Not Use Account Shared Access Signature +dotnet_diagnostic.CA5375.severity = warning + +# CA5376: Use SharedAccessProtocol HttpsOnly +dotnet_diagnostic.CA5376.severity = warning + +# CA5377: Use Container Level Access Policy +dotnet_diagnostic.CA5377.severity = warning + +# CA5378: Do not disable ServicePointManagerSecurityProtocols +dotnet_diagnostic.CA5378.severity = warning + +# CA5379: Ensure Key Derivation Function algorithm is sufficiently strong +dotnet_diagnostic.CA5379.severity = warning + +# CA5380: Do Not Add Certificates To Root Store +dotnet_diagnostic.CA5380.severity = warning + +# CA5381: Ensure Certificates Are Not Added To Root Store +dotnet_diagnostic.CA5381.severity = warning + +# CA5382: Use Secure Cookies In ASP.NET Core +dotnet_diagnostic.CA5382.severity = warning + +# CA5383: Ensure Use Secure Cookies In ASP.NET Core +dotnet_diagnostic.CA5383.severity = warning + +# CA5384: Do Not Use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5384.severity = warning + +# CA5385: Use Rivest-Shamir-Adleman (RSA) Algorithm With Sufficient Key Size +dotnet_diagnostic.CA5385.severity = warning + +# CA5386: Avoid hardcoding SecurityProtocolType value +dotnet_diagnostic.CA5386.severity = warning + +# CA5387: Do Not Use Weak Key Derivation Function With Insufficient Iteration Count +dotnet_diagnostic.CA5387.severity = warning + +# CA5388: Ensure Sufficient Iteration Count When Using Weak Key Derivation Function +dotnet_diagnostic.CA5388.severity = warning + +# CA5389: Do Not Add Archive Item's Path To The Target File System Path +dotnet_diagnostic.CA5389.severity = warning + +# CA5390: Do not hard-code encryption key +dotnet_diagnostic.CA5390.severity = warning + +# CA5391: Use antiforgery tokens in ASP.NET Core MVC controllers +dotnet_diagnostic.CA5391.severity = warning + +# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +dotnet_diagnostic.CA5392.severity = warning + +# CA5393: Do not use unsafe DllImportSearchPath value +dotnet_diagnostic.CA5393.severity = warning + +# CA5394: Do not use insecure randomness +dotnet_diagnostic.CA5394.severity = warning + +# CA5395: Miss HttpVerb attribute for action methods +dotnet_diagnostic.CA5395.severity = warning + +# CA5396: Set HttpOnly to true for HttpCookie +dotnet_diagnostic.CA5396.severity = warning + +# CA5397: Do not use deprecated SslProtocols values +dotnet_diagnostic.CA5397.severity = warning + +# CA5398: Avoid hardcoded SslProtocols values +dotnet_diagnostic.CA5398.severity = warning + +# CA5399: HttpClients should enable certificate revocation list checks +dotnet_diagnostic.CA5399.severity = warning + +# CA5400: Ensure HttpClient certificate revocation list check is not disabled +dotnet_diagnostic.CA5400.severity = warning + +# CA5401: Do not use CreateEncryptor with non-default IV +dotnet_diagnostic.CA5401.severity = warning + +# CA5402: Use CreateEncryptor with the default IV +dotnet_diagnostic.CA5402.severity = warning + +# CA5403: Do not hard-code certificate +dotnet_diagnostic.CA5403.severity = warning + +# CA5404: Do not disable token validation checks +dotnet_diagnostic.CA5404.severity = warning + +# CA5405: Do not always skip token validation in delegates +dotnet_diagnostic.CA5405.severity = warning + +# CA1032: Implement standard exception constructors +dotnet_diagnostic.CA1032.severity = warning + +# CA1200: Avoid using cref tags with a prefix +dotnet_diagnostic.CA1200.severity = warning + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = warning + +# CA1311: Specify a culture or use an invariant version +dotnet_diagnostic.CA1311.severity = warning + +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = warning + +# CA1508: Avoid dead conditional code +dotnet_diagnostic.CA1508.severity = warning + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning + +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = warning + +# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +dotnet_diagnostic.CA1824.severity = warning + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = warning + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = warning + +# CA1851: Possible multiple enumerations of 'IEnumerable' collection +dotnet_diagnostic.CA1851.severity = warning + +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = warning + +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = warning + +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = warning + +# CA1865: Use char overload +dotnet_diagnostic.CA1865.severity = warning + +# CA1866: Use char overload +dotnet_diagnostic.CA1866.severity = warning + +# CA1867: Use char overload +dotnet_diagnostic.CA1867.severity = warning + +# CA1870: Use a cached 'SearchValues' instance +dotnet_diagnostic.CA1870.severity = warning + +# CA2014: Do not use stackalloc in loops +dotnet_diagnostic.CA2014.severity = warning + +# CA2016: Forward the 'CancellationToken' parameter to methods +dotnet_diagnostic.CA2016.severity = warning + +# CA2020: Prevent behavioral change +dotnet_diagnostic.CA2020.severity = warning + +# CA2234: Pass system uri objects instead of strings +dotnet_diagnostic.CA2234.severity = warning + +# CA2252: This API requires opting into preview features +dotnet_diagnostic.CA2252.severity = warning + +# CA2260: Use correct type parameter +dotnet_diagnostic.CA2260.severity = warning + +# CA2352: Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2352.severity = warning + +# CA2353: Unsafe DataSet or DataTable in serializable type +dotnet_diagnostic.CA2353.severity = warning + +# CA2354: Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2354.severity = warning + +# CA2355: Unsafe DataSet or DataTable type found in deserializable object graph +dotnet_diagnostic.CA2355.severity = warning + +# CA2356: Unsafe DataSet or DataTable type in web deserializable object graph +dotnet_diagnostic.CA2356.severity = warning + +# CA2362: Unsafe DataSet or DataTable in auto-generated serializable type can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2362.severity = warning diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index f0c0d87..9d9fbbe 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -1,6 +1,8 @@  -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" @@ -9,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F030ACD-F87C-4E83-9A68-4CC5DF03AD90}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,4 +39,7 @@ Global {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FDA7E0D-9C75-4874-988E-6592CD28F76C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection EndGlobal diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs new file mode 100644 index 0000000..ba31b9d --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class FrostFsException : Exception +{ + public FrostFsException() + { + } + + public FrostFsException(string message) : base(message) + { + } + + public FrostFsException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs index f2a8ea7..c15e591 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs +++ b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs @@ -2,6 +2,17 @@ using System; namespace FrostFS.SDK.ClientV2; -public class InvalidObjectException() : Exception() +public class InvalidObjectException : Exception { + public InvalidObjectException() + { + } + + public InvalidObjectException(string message) : base(message) + { + } + + public InvalidObjectException(string message, Exception innerException) : base(message, innerException) + { + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs index ad3c63e..ce7f19a 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs +++ b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs @@ -2,7 +2,24 @@ using System; namespace FrostFS.SDK.ClientV2; -public class ResponseException(FrostFsResponseStatus status) : Exception() +public class ResponseException : Exception { - public FrostFsResponseStatus Status { get; set; } = status; + public FrostFsResponseStatus? Status { get; private set; } + + public ResponseException() + { + } + + public ResponseException(FrostFsResponseStatus status) + { + Status = status; + } + + public ResponseException(string message) : base(message) + { + } + + public ResponseException(string message, Exception innerException) : base(message, innerException) + { + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index 5f423dd..b16e7e7 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -4,14 +4,23 @@ netstandard2.0 12.0 enable + AllEnabledByDefault - - true - + + true + + + + true + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/FrostFS.SDK.ClientV2/Client.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs similarity index 79% rename from src/FrostFS.SDK.ClientV2/Client.cs rename to src/FrostFS.SDK.ClientV2/FrostFSClient.cs index 8209571..4a680de 100644 --- a/src/FrostFS.SDK.ClientV2/Client.cs +++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs @@ -3,6 +3,9 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using Frostfs.V2.Ape; +using Frostfs.V2.Apemanager; + using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; @@ -15,35 +18,33 @@ using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Options; -using Frostfs.V2.Apemanager; -using Frostfs.V2.Ape; namespace FrostFS.SDK.ClientV2; -public class Client : IFrostFSClient +public class FrostFSClient : IFrostFSClient { private bool isDisposed; - + internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; } - + internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; } internal APEManagerService.APEManagerServiceClient? ApeManagerServiceClient { get; set; } internal SessionService.SessionServiceClient? SessionServiceClient { get; set; } - + internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } internal ClientEnvironment ClientCtx { get; set; } public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { - return new Client(clientOptions, channelOptions); + return new FrostFSClient(clientOptions, channelOptions); } public static IFrostFSClient GetSingleOwnerInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { - return new Client(clientOptions, channelOptions); + return new FrostFSClient(clientOptions, channelOptions); } /// @@ -64,10 +65,15 @@ public class Client : IFrostFSClient ContainerService.ContainerServiceClient containerService, ObjectService.ObjectServiceClient objectService) { - return new Client(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService); + if (clientOptions is null) + { + throw new ArgumentNullException(nameof(clientOptions)); + } + + return new FrostFSClient(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService); } - private Client( + private FrostFSClient( IOptions settings, GrpcChannelOptions? channelOptions, ContainerService.ContainerServiceClient containerService, @@ -75,6 +81,11 @@ public class Client : IFrostFSClient SessionService.SessionServiceClient sessionService, ObjectService.ObjectServiceClient objectService) { + if (settings is null) + { + throw new ArgumentNullException(nameof(settings)); + } + var ecdsaKey = settings.Value.Key.LoadWif(); FrostFsOwner.FromKey(ecdsaKey); @@ -85,13 +96,13 @@ public class Client : IFrostFSClient channel: InitGrpcChannel(settings.Value.Host, channelOptions), version: new FrostFsVersion(2, 13)); - ContainerServiceClient = containerService; - NetmapServiceClient = netmapService; - SessionServiceClient = sessionService; - ObjectServiceClient = objectService; + ContainerServiceClient = containerService ?? throw new ArgumentNullException(nameof(containerService)); + NetmapServiceClient = netmapService ?? throw new ArgumentNullException(nameof(netmapService)); + SessionServiceClient = sessionService ?? throw new ArgumentNullException(nameof(sessionService)); + ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService)); } - private Client(IOptions options, GrpcChannelOptions? channelOptions) + private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) { var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); @@ -110,14 +121,14 @@ public class Client : IFrostFSClient // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); } - private Client(IOptions options, GrpcChannelOptions? channelOptions) + private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) { var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); clientSettings.Validate(); var ecdsaKey = clientSettings.Key.LoadWif(); - + var channel = InitGrpcChannel(clientSettings.Host, channelOptions); ClientCtx = new ClientEnvironment( @@ -128,7 +139,7 @@ public class Client : IFrostFSClient version: new FrostFsVersion(2, 13)); // TODO: define timeout logic - CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20)}); + // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); } public void Dispose() @@ -149,18 +160,31 @@ public class Client : IFrostFSClient #region ApeManagerImplementation public Task AddChainAsync(PrmApeChainAdd args) { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + var service = GetApeManagerService(args); return service.AddChainAsync(args); } public Task RemoveChainAsync(PrmApeChainRemove args) { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + var service = GetApeManagerService(args); return service.RemoveChainAsync(args); } public Task ListChainAsync(PrmApeChainList args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetApeManagerService(args); return service.ListChainAsync(args); } @@ -169,6 +193,9 @@ public class Client : IFrostFSClient #region ContainerImplementation public Task GetContainerAsync(PrmContainerGet args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetContainerService(args); return service.GetContainerAsync(args); } @@ -176,18 +203,24 @@ public class Client : IFrostFSClient public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { args ??= new PrmContainerGetAll(); - var service = GetContainerService(args); + var service = GetContainerService(args); return service.ListContainersAsync(args); } public Task CreateContainerAsync(PrmContainerCreate args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetContainerService(args); return service.CreateContainerAsync(args); } public Task DeleteContainerAsync(PrmContainerDelete args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetContainerService(args); return service.DeleteContainerAsync(args); } @@ -219,36 +252,54 @@ public class Client : IFrostFSClient #region ObjectImplementation public Task GetObjectHeadAsync(PrmObjectHeadGet args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.GetObjectHeadAsync(args); } public Task GetObjectAsync(PrmObjectGet args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.GetObjectAsync(args); } public Task PutObjectAsync(PrmObjectPut args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.PutObjectAsync(args); } public Task PutSingleObjectAsync(PrmSingleObjectPut args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.PutSingleObjectAsync(args); } public Task DeleteObjectAsync(PrmObjectDelete args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.DeleteObjectAsync(args); } public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetObjectService(args); return service.SearchObjectsAsync(args); } @@ -257,17 +308,23 @@ public class Client : IFrostFSClient #region SessionImplementation public async Task CreateSessionAsync(PrmSessionCreate args) { - var session = await CreateSessionInternalAsync(args); + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var session = await CreateSessionInternalAsync(args).ConfigureAwait(false); var token = session.Serialize(); - + return new FrostFsSessionToken(token); } - internal Task CreateSessionInternalAsync(PrmSessionCreate args) + internal Task CreateSessionInternalAsync(PrmSessionCreate args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + var service = GetSessionService(args); return service.CreateSessionAsync(args); - } + } #endregion #region ToolsImplementation @@ -283,20 +340,24 @@ public class Client : IFrostFSClient private async void CheckFrostFsVersionSupport(Context? ctx = default) { var args = new PrmNodeInfo { Context = ctx }; + + if (ctx?.Version == null) + throw new InvalidObjectException(nameof(ctx.Version)); + var service = GetNetmapService(args); - var localNodeInfo = await service.GetLocalNodeInfoAsync(args); - - if (!localNodeInfo.Version.IsSupported(args.Context!.Version)) + var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false); + + if (!localNodeInfo.Version.IsSupported(ctx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; - throw new ApplicationException(msg); + throw new FrostFsException(msg); } } private CallInvoker? SetupEnvironment(IContext ctx) { if (isDisposed) - throw new Exception("Client is disposed."); + throw new InvalidObjectException("Client is disposed."); ctx.Context ??= new Context(); @@ -304,14 +365,14 @@ public class Client : IFrostFSClient { if (ClientCtx.Key == null) { - throw new Exception("Key is not initialized."); + throw new InvalidObjectException("Key is not initialized."); } ctx.Context.Key = ClientCtx.Key.ECDsaKey; } if (ctx.Context.OwnerId == null) - { + { ctx.Context.OwnerId = ClientCtx.Owner ?? FrostFsOwner.FromKey(ctx.Context.Key); } @@ -319,7 +380,7 @@ public class Client : IFrostFSClient { if (ClientCtx.Version == null) { - throw new Exception("Version is not initialized."); + throw new InvalidObjectException("Version is not initialized."); } ctx.Context.Version = ClientCtx.Version; @@ -336,7 +397,7 @@ public class Client : IFrostFSClient if (ctx.Context.Callback != null) callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback)); - + return callInvoker; CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor) @@ -382,7 +443,7 @@ public class Client : IFrostFSClient private ContainerServiceProvider GetContainerService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupEnvironment(ctx); var client = ContainerServiceClient ?? (callInvoker != null ? new ContainerService.ContainerServiceClient(callInvoker) : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); @@ -408,7 +469,7 @@ public class Client : IFrostFSClient if (channelOptions != null) return GrpcChannel.ForAddress(uri, channelOptions); - + return GrpcChannel.ForAddress(uri, new GrpcChannelOptions { HttpHandler = new HttpClientHandler() diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs index dc439f9..9b3199c 100644 --- a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs @@ -14,6 +14,11 @@ public class MetricsInterceptor(Action callback) : Interceptor ClientInterceptorContext context, AsyncUnaryCallContinuation continuation) { + if (continuation is null) + { + throw new ArgumentNullException(nameof(continuation)); + } + var call = continuation(request, context); return new AsyncUnaryCall( @@ -28,7 +33,10 @@ public class MetricsInterceptor(Action callback) : Interceptor ClientInterceptorContext context, AsyncClientStreamingCallContinuation continuation) { - var call = continuation(context); + if (continuation is null) + throw new ArgumentNullException(nameof(continuation)); + + var call = continuation(context); return new AsyncClientStreamingCall( call.RequestStream, @@ -43,31 +51,31 @@ public class MetricsInterceptor(Action callback) : Interceptor { var watch = new Stopwatch(); watch.Start(); - - var response = await call.ResponseAsync; + + var response = await call.ResponseAsync.ConfigureAwait(false); watch.Stop(); - var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency; callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); - return response; + return response; } private async Task HandleStreamResponse(AsyncClientStreamingCall call) { var watch = new Stopwatch(); watch.Start(); - - var response = await call.ResponseAsync; + + var response = await call.ResponseAsync.ConfigureAwait(false); watch.Stop(); - var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; + var elapsed = watch.ElapsedTicks * 1_000_000 / Stopwatch.Frequency; callback(new CallStatistics { MethodName = call.ToString(), ElapsedMicroSeconds = elapsed }); - return response; + return response; } } diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index a580149..3af0c0b 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -36,21 +36,21 @@ public interface IFrostFSClient : IDisposable Task DeleteContainerAsync(PrmContainerDelete args); #endregion - + #region Object Task GetObjectHeadAsync(PrmObjectHeadGet args); Task GetObjectAsync(PrmObjectGet args); Task PutObjectAsync(PrmObjectPut args); - + Task PutSingleObjectAsync(PrmSingleObjectPut args); Task DeleteObjectAsync(PrmObjectDelete args); IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); #endregion - + #region Tools FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx); #endregion diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs index 181de40..e631472 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Container.cs @@ -9,14 +9,12 @@ public static class ContainerMapper { public static FrostFsContainerInfo ToModel(this Container.Container container) { - if (!Enum.IsDefined(typeof(BasicAcl),(int)container.BasicAcl)) - throw new ArgumentException($"Unknown BasicACL rule. Value: '{container.BasicAcl}'."); - - BasicAcl acl = (BasicAcl)container.BasicAcl; + if (container == null) + throw new ArgumentNullException(nameof(container)); - return new FrostFsContainerInfo(acl, + return new FrostFsContainerInfo( container.PlacementPolicy.ToModel(), - container.Attributes?.Select(a => new FrostFsAttribute(a.Key, a.Value)).ToList(), + container.Attributes?.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToArray(), container.Version?.ToModel(), container.OwnerId?.ToModel(), container.Nonce?.ToUuid()); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index f5feccc..86449e2 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -17,19 +17,23 @@ public static class ContainerIdMapper public static ContainerID ToMessage(this FrostFsContainerId model) { - if (model.Value == null) + if (model is null) + { throw new ArgumentNullException(nameof(model)); - - if (!Cache.Containers.TryGetValue(model.Value, out ContainerID? message)) + } + + var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model)); + + if (!Cache.Containers.TryGetValue(containerId, out ContainerID? message)) { message = new ContainerID { - Value = ByteString.CopyFrom(Base58.Decode(model.Value)) + Value = ByteString.CopyFrom(Base58.Decode(containerId)) }; - Cache.Containers.Set(model.Value, message, _oneHourExpiration); + Cache.Containers.Set(containerId, message, _oneHourExpiration); } - return message!; + return message!; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs index 6b4901c..5bf994e 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs @@ -1,3 +1,5 @@ +using System; + using FrostFS.Session; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -6,11 +8,16 @@ public static class MetaHeaderMapper { public static RequestMetaHeader ToMessage(this MetaHeader metaHeader) { + if (metaHeader is null) + { + throw new ArgumentNullException(nameof(metaHeader)); + } + return new RequestMetaHeader { Version = metaHeader.Version.ToMessage(), Epoch = (uint)metaHeader.Epoch, Ttl = (uint)metaHeader.Ttl }; - } + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs index 31a8a0f..6729501 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using FrostFS.Netmap; @@ -8,6 +9,11 @@ public static class NetmapMapper { public static FrostFsNetmapSnapshot ToModel(this NetmapSnapshotResponse netmap) { + if (netmap is null) + { + throw new ArgumentNullException(nameof(netmap)); + } + return new FrostFsNetmapSnapshot( netmap.Body.Netmap.Epoch, netmap.Body.Netmap.Nodes diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs index 86e5ff0..d882127 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs @@ -10,11 +10,21 @@ public static class NodeInfoMapper { public static FrostFsNodeInfo ToModel(this LocalNodeInfoResponse.Types.Body node) { + if (node is null) + { + throw new ArgumentNullException(nameof(node)); + } + return node.NodeInfo.ToModel(node.Version); } public static FrostFsNodeInfo ToModel(this NodeInfo nodeInfo, Refs.Version version) { + if (nodeInfo is null) + { + throw new ArgumentNullException(nameof(nodeInfo)); + } + NodeState state = nodeInfo.State switch { NodeInfo.Types.State.Unspecified => NodeState.Unspecified, diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs index 2f66ed8..c44a397 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using FrostFS.Netmap; @@ -6,26 +7,13 @@ namespace FrostFS.SDK.ClientV2; public static class PlacementPolicyMapper { - public static PlacementPolicy ToMessage(this FrostFsPlacementPolicy placementPolicy) - { - var pp = new PlacementPolicy - { - Filters = { }, - Selectors = { }, - Replicas = { }, - Unique = placementPolicy.Unique - }; - - foreach (var replica in placementPolicy.Replicas) - { - pp.Replicas.Add(replica.ToMessage()); - } - - return pp; - } - public static FrostFsPlacementPolicy ToModel(this PlacementPolicy placementPolicy) { + if (placementPolicy is null) + { + throw new ArgumentNullException(nameof(placementPolicy)); + } + return new FrostFsPlacementPolicy( placementPolicy.Unique, placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs index 422891d..d9ea0cd 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs @@ -1,3 +1,5 @@ +using System; + using FrostFS.Netmap; namespace FrostFS.SDK.ClientV2; @@ -15,6 +17,11 @@ public static class ReplicaMapper public static FrostFsReplica ToModel(this Replica replica) { + if (replica is null) + { + throw new ArgumentNullException(nameof(replica)); + } + return new FrostFsReplica((int)replica.Count, replica.Selector); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs index 0e10ffe..d9f5d13 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs @@ -8,5 +8,5 @@ internal static class ObjectMapper { ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray()) }; - } + } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs index de596ad..85c7c5a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs @@ -1,11 +1,18 @@ +using System; + using FrostFS.Object; namespace FrostFS.SDK.ClientV2.Mappers.GRPC; public static class ObjectAttributeMapper { - public static Header.Types.Attribute ToMessage(this FrostFsAttribute attribute) + public static Header.Types.Attribute ToMessage(this FrostFsAttributePair attribute) { + if (attribute is null) + { + throw new ArgumentNullException(nameof(attribute)); + } + return new Header.Types.Attribute { Key = attribute.Key, @@ -13,8 +20,13 @@ public static class ObjectAttributeMapper }; } - public static FrostFsAttribute ToModel(this Header.Types.Attribute attribute) + public static FrostFsAttributePair ToModel(this Header.Types.Attribute attribute) { - return new FrostFsAttribute(attribute.Key, attribute.Value); + if (attribute is null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + return new FrostFsAttributePair(attribute.Key, attribute.Value); } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs index 0303213..76d1c92 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs @@ -8,6 +8,11 @@ public static class ObjectFilterMapper { public static SearchRequest.Types.Body.Types.Filter ToMessage(this IObjectFilter filter) { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + var objMatchTypeName = filter.MatchType switch { FrostFsMatchType.Unspecified => MatchType.Unspecified, @@ -25,5 +30,5 @@ public static class ObjectFilterMapper Key = filter.Key, Value = filter.GetSerializedValue() }; - } + } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs index fb4c77d..bdeb862 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using System.Linq; using FrostFS.Object; @@ -10,6 +11,11 @@ public static class ObjectHeaderMapper { public static FrostFsObjectHeader ToModel(this Header header) { + if (header is null) + { + throw new ArgumentNullException(nameof(header)); + } + var objTypeName = header.ObjectType switch { ObjectType.Regular => FrostFsObjectType.Regular, @@ -22,15 +28,15 @@ public static class ObjectHeaderMapper if (header.Split != null) { - split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid())) - { - Parent = header.Split.Parent?.ToModel(), - ParentHeader = header.Split.ParentHeader?.ToModel(), - Previous = header.Split.Previous?.ToModel() - }; + var children = header.Split.Children.Count != 0 ? new ReadOnlyCollection( + header.Split.Children.Select(x => x.ToModel()).ToList()) : null; - if (header.Split.Children.Count != 0) - split.Children.AddRange(header.Split.Children.Select(x => x.ToModel())); + split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid()), + header.Split.Previous?.ToModel(), + header.Split.Parent?.ToModel(), + header.Split.ParentHeader?.ToModel(), + null, + children); } var model = new FrostFsObjectHeader( @@ -40,10 +46,10 @@ public static class ObjectHeaderMapper split, header.OwnerId.ToModel(), header.Version.ToModel()) - { + { PayloadLength = header.PayloadLength, }; return model; - } + } } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs index d20a915..10f8409 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs @@ -1,3 +1,5 @@ +using System; + using FrostFS.Refs; using Google.Protobuf; @@ -8,6 +10,11 @@ public static class ObjectIdMapper { public static ObjectID ToMessage(this FrostFsObjectId objectId) { + if (objectId is null) + { + throw new ArgumentNullException(nameof(objectId)); + } + return new ObjectID { Value = ByteString.CopyFrom(objectId.ToHash()) @@ -16,6 +23,11 @@ public static class ObjectIdMapper public static FrostFsObjectId ToModel(this ObjectID objectId) { + if (objectId is null) + { + throw new ArgumentNullException(nameof(objectId)); + } + return FrostFsObjectId.FromHash(objectId.Value.ToByteArray()); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs index ff81321..6f3276d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs @@ -17,6 +17,11 @@ public static class OwnerIdMapper public static OwnerID ToMessage(this FrostFsOwner model) { + if (model is null) + { + throw new ArgumentNullException(nameof(model)); + } + if (!Cache.Owners.TryGetValue(model, out OwnerID? message)) { message = new OwnerID @@ -32,10 +37,15 @@ public static class OwnerIdMapper public static FrostFsOwner ToModel(this OwnerID message) { + if (message is null) + { + throw new ArgumentNullException(nameof(message)); + } + if (!Cache.Owners.TryGetValue(message, out FrostFsOwner? model)) { model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray())); - + Cache.Owners.Set(message, model, _oneHourExpiration); } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs index 97b1c69..9f4a36f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs @@ -1,3 +1,5 @@ +using System; + using Google.Protobuf; namespace FrostFS.SDK.ClientV2; @@ -6,8 +8,13 @@ public static class SessionMapper { public static byte[] Serialize(this Session.SessionToken token) { + if (token is null) + { + throw new ArgumentNullException(nameof(token)); + } + byte[] bytes = new byte[token.CalculateSize()]; - CodedOutputStream stream = new(bytes); + using CodedOutputStream stream = new(bytes); token.WriteTo(stream); return bytes; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs index 0b280cf..d3bf988 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs @@ -8,13 +8,17 @@ public static class SignatureMapper { public static Refs.Signature ToMessage(this FrostFsSignature signature) { + if (signature is null) + { + throw new ArgumentNullException(nameof(signature)); + } + var scheme = signature.Scheme switch { SignatureScheme.EcdsaRfc6979Sha256 => Refs.SignatureScheme.EcdsaRfc6979Sha256, SignatureScheme.EcdsaRfc6979Sha256WalletConnect => Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect, SignatureScheme.EcdsaSha512 => Refs.SignatureScheme.EcdsaSha512, - _ => throw new ArgumentException(message: $"Unexpected enum value: {signature.Scheme}", - paramName: nameof(signature.Scheme)) + _ => throw new ArgumentException(nameof(signature.Scheme), $"Unexpected enum value: {signature.Scheme}") }; return new Refs.Signature diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs index d3555ac..ad9ea7d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Status.cs @@ -6,7 +6,7 @@ public static class StatusMapper { public static FrostFsResponseStatus ToModel(this Status.Status status) { - if (status is null) + if (status is null) return new FrostFsResponseStatus(FrostFsStatusCode.Success); var codeName = Enum.GetName(typeof(FrostFsStatusCode), status.Code); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs index f17509a..d4cc254 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/Version.cs @@ -9,10 +9,15 @@ public static class VersionMapper { private static readonly Hashtable _cacheMessages = []; private static readonly Hashtable _cacheModels = []; - private static SpinLock _spinlock = new(); + private static SpinLock _spinlock; public static Version ToMessage(this FrostFsVersion model) { + if (model is null) + { + throw new System.ArgumentNullException(nameof(model)); + } + var key = model.Major << 16 + model.Minor; if (!_cacheMessages.ContainsKey(key)) @@ -46,6 +51,11 @@ public static class VersionMapper public static FrostFsVersion ToModel(this Version message) { + if (message is null) + { + throw new System.ArgumentNullException(nameof(message)); + } + var key = (int)message.Major << 16 + (int)message.Minor; if (!_cacheModels.ContainsKey(key)) diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs index 0d47993..e9ce527 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs @@ -4,7 +4,7 @@ using Frostfs.V2.Ape; namespace FrostFS.SDK.ClientV2 { - public struct FrostFsChainTarget(FrostFsTargetType type, string name) + public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable { private ChainTarget? chainTarget; @@ -33,5 +33,31 @@ namespace FrostFS.SDK.ClientV2 _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)), }; } + + public override readonly bool Equals(object obj) + { + var target = (FrostFsChainTarget)obj; + return Equals(target); + } + + public override readonly int GetHashCode() + { + return $"{Name}{Type}".GetHashCode(); + } + + public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right) + { + return left.Equals(right); + } + + public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right) + { + return !(left == right); + } + + public readonly bool Equals(FrostFsChainTarget other) + { + return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal); + } } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs index 0c21521..d6b4b1d 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2 { - public struct FrostFsChain (byte[] raw) + public struct FrostFsChain(byte[] raw) : System.IEquatable { private ByteString? grpcRaw; @@ -12,5 +12,31 @@ namespace FrostFS.SDK.ClientV2 { 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; + } } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs index 56617d3..03b1521 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2 { - public enum FrostFsTargetType + public enum FrostFsTargetType { Undefined = 0, Namespace, diff --git a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs index 650c7d6..b1faf79 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; using System.Text; namespace FrostFS.SDK; @@ -17,18 +18,24 @@ public class ClientSettings ThrowException(errors); } - protected List? CheckFields() + protected Collection? CheckFields() { - List? errors = null; - if (string.IsNullOrWhiteSpace(Host)) - (errors ??= []).Add(string.Format(errorTemplate, nameof(Host))); + { + var error = string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host)); + return new Collection([error]); + } - return errors; + return null; } - protected static void ThrowException(List errors) + protected static void ThrowException(Collection errors) { + if (errors is null) + { + throw new ArgumentNullException(nameof(errors)); + } + StringBuilder messages = new(); foreach (var error in errors) @@ -51,12 +58,12 @@ public class SingleOwnerClientSettings : ClientSettings ThrowException(errors); } - protected new List? CheckFields() + protected new Collection? CheckFields() { - List? errors = base.CheckFields(); + Collection? errors = base.CheckFields(); if (string.IsNullOrWhiteSpace(Key)) - (errors ??= []).Add(string.Format(errorTemplate, nameof(Key))); + (errors ??= []).Add(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key))); return errors; } diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs index 895f25a..1ebbe04 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs @@ -1,8 +1,7 @@ using FrostFS.Refs; -using FrostFS.SDK.Cryptography; - -using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; @@ -21,21 +20,18 @@ public class FrostFsContainerId this.containerID = id; } - public string Value + public string GetValue() { - get + if (this.modelId != null) + return this.modelId; + + if (containerID != null) { - if (this.modelId != null) - return this.modelId; - - if (containerID != null) - { - this.modelId = Base58.Encode(containerID.Value.ToByteArray()); - return this.modelId; - } - - throw new InvalidObjectException(); + this.modelId = Base58.Encode(containerID.Value.ToByteArray()); + return this.modelId; } + + throw new InvalidObjectException(); } internal ContainerID ContainerID @@ -57,6 +53,6 @@ public class FrostFsContainerId public override string ToString() { - return Value; + return GetValue(); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs index 74d0e41..e7336b2 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.Cryptography; using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; using Google.Protobuf; @@ -12,61 +12,59 @@ namespace FrostFS.SDK; public class FrostFsContainerInfo { - private FrostFS.Container.Container.Types.Attribute[]? grpsAttributes; - private List? attributes; + private Container.Container.Types.Attribute[]? grpsAttributes; + private ReadOnlyCollection? attributes; private FrostFsPlacementPolicy? placementPolicy; private Guid? nonce; - private Container.Container container; + private Container.Container? container; public FrostFsContainerInfo( - BasicAcl basicAcl, FrostFsPlacementPolicy placementPolicy, - List? attributes = null, + FrostFsAttributePair[]? attributes = null, FrostFsVersion? version = null, FrostFsOwner? owner = null, Guid? nonce = null) { - BasicAcl = basicAcl; this.placementPolicy = placementPolicy; - this.attributes = attributes; Version = version; Owner = owner; this.nonce = nonce; + + if (attributes != null) + this.attributes = new ReadOnlyCollection(attributes); } internal FrostFsContainerInfo(Container.Container container) { this.container = container; } - - public Guid Nonce + + public Guid Nonce { get { nonce ??= container?.Nonce != null ? container.Nonce.ToUuid() : Guid.NewGuid(); return nonce.Value; } - } - - public BasicAcl BasicAcl { get; private set; } + } public FrostFsPlacementPolicy? PlacementPolicy { get { - placementPolicy ??= container.PlacementPolicy?.ToModel(); + placementPolicy ??= container?.PlacementPolicy?.ToModel(); return placementPolicy; } } - public List? Attributes + public ReadOnlyCollection? Attributes { get { if (attributes == null && grpsAttributes != null) - attributes = grpsAttributes.Select(a => new FrostFsAttribute(a.Key, a.Value)).ToList(); - + attributes = new ReadOnlyCollection(grpsAttributes.Select(a => new FrostFsAttributePair(a.Key, a.Value)).ToList()); + return attributes; } } @@ -74,8 +72,8 @@ public class FrostFsContainerInfo public FrostFsVersion? Version { get; private set; } public FrostFsOwner? Owner { get; private set; } - - internal Container.Container.Types.Attribute[]? GetGrpsAttributes() + + internal Container.Container.Types.Attribute[]? GetGrpsAttributes() { grpsAttributes ??= Attributes? .Select(a => new Container.Container.Types.Attribute { Key = a.Key, Value = a.Value }) @@ -88,10 +86,14 @@ public class FrostFsContainerInfo { if (this.container == null) { + if (PlacementPolicy == null) + { + throw new InvalidObjectException("PlacementPolicy is null"); + } + this.container = new Container.Container() { - BasicAcl = (uint)BasicAcl, - PlacementPolicy = PlacementPolicy.ToMessage(), + PlacementPolicy = PlacementPolicy.Value.GetPolicy(), Nonce = ByteString.CopyFrom(Nonce.ToBytes()), OwnerId = Owner?.OwnerID, Version = Version?.Version diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs b/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs deleted file mode 100644 index dd3252b..0000000 --- a/src/FrostFS.SDK.ClientV2/Models/Enums/BasicAcl.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel; - -namespace FrostFS.SDK; - -public enum BasicAcl -{ - [Description("Not defined ACL")] - NotDefined = 0x00000000, - - [Description("Basic ACL for private container")] - Private = 0x1C8C8CCC, - - [Description("Basic ACL for public RO container")] - PublicRO = 0x1FBF8CFF, - - [Description("Basic ACL for public RW container")] - PublicRW = 0x1FBFBFFF, - - [Description("Basic ACL for public append container")] - PublicAppend = 0x1FBF9FFF, -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs b/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs index 82017f9..fdeac99 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs @@ -1,20 +1,21 @@ -using FrostFS.SDK.Cryptography; using System; +using FrostFS.SDK.Cryptography; + namespace FrostFS.SDK; public class CheckSum { - // type is always Sha256 - public byte[]? Hash { get; set; } + private byte[]? hash; + private string? text; public static CheckSum CreateCheckSum(byte[] content) { - return new CheckSum { Hash = content.Sha256() }; + return new CheckSum { hash = content.Sha256() }; } public override string ToString() { - return BitConverter.ToString(Hash).Replace("-", ""); + return text ??= BitConverter.ToString(hash).Replace("-", ""); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs b/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs index 332a3e3..28c8cf0 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs @@ -1,8 +1,8 @@ namespace FrostFS.SDK; -public class Constants +public static class Constants { - public const int ObjectChunkSize = 3 * (1 << 20); + public const int ObjectChunkSize = 3 * (1 << 20); public const int Sha256HashLength = 32; // HeaderPrefix is a prefix of key to object header value or property. diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs index 410598c..62f7fb2 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs @@ -1,28 +1,89 @@ + using System; using System.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.ClientV2; + namespace FrostFS.SDK; -public class FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) : IComparable +public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) + : IEquatable { + private PlacementPolicy policy; + public FrostFsReplica[] Replicas { get; private set; } = replicas; public bool Unique { get; private set; } = unique; - public int CompareTo(FrostFsPlacementPolicy other) + public override readonly bool Equals(object obj) { - var notEqual = other == null - || Unique != other.Unique - || Replicas.Length != other.Replicas.Length; + if (obj is null) + return false; + + var other = (FrostFsPlacementPolicy)obj; + + return Equals(other); + } + + public PlacementPolicy GetPolicy() + { + if (policy == null) + { + policy = new PlacementPolicy + { + Filters = { }, + Selectors = { }, + Replicas = { }, + Unique = Unique + }; + + foreach (var replica in Replicas) + { + policy.Replicas.Add(replica.ToMessage()); + } + } + + return policy; + } + + //public static FrostFsPlacementPolicy ToModel(placementPolicy) + //{ + // return new FrostFsPlacementPolicy( + // placementPolicy.Unique, + // placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() + // ); + //} + + + public override readonly int GetHashCode() + { + return Unique ? 17 : 0 + Replicas.GetHashCode(); + } + + public static bool operator ==(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right) + { + return left.Equals(right); + } + + public static bool operator !=(FrostFsPlacementPolicy left, FrostFsPlacementPolicy right) + { + return !(left == right); + } + + public readonly bool Equals(FrostFsPlacementPolicy other) + { + var notEqual = Unique != other.Unique + || Replicas.Length != other.Replicas.Length; if (notEqual) - return 1; + return false; foreach (var replica in Replicas) { - if (!other!.Replicas.Any(r => r.Count == replica.Count && r.Selector == replica.Selector)) - return 1; + if (!other.Replicas.Any(r => r.Equals(replica))) + return false; } - return 0; + return true; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs index 8fd1bd9..fa2f8db 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs @@ -1,6 +1,8 @@ +using System; + namespace FrostFS.SDK; -public class FrostFsReplica +public struct FrostFsReplica : IEquatable { public int Count { get; set; } public string Selector { get; set; } @@ -12,4 +14,34 @@ public class FrostFsReplica Count = count; Selector = selector; } + + public override readonly bool Equals(object obj) + { + if (obj is null) + return false; + + var other = (FrostFsReplica)obj; + + return Count == other.Count && Selector == other.Selector; + } + + public override readonly int GetHashCode() + { + return Count + Selector.GetHashCode(); + } + + public static bool operator ==(FrostFsReplica left, FrostFsReplica right) + { + return left.Equals(right); + } + + public static bool operator !=(FrostFsReplica left, FrostFsReplica right) + { + return !(left == right); + } + + public readonly bool Equals(FrostFsReplica other) + { + return Count == other.Count && Selector == other.Selector; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs index da66403..e63d9ac 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs @@ -5,7 +5,7 @@ namespace FrostFS.SDK; public class FrostFsVersion(int major, int minor) { - public Version version; + private Version? version; public int Major { get; set; } = major; public int Minor { get; set; } = minor; @@ -21,6 +21,11 @@ public class FrostFsVersion(int major, int minor) public bool IsSupported(FrostFsVersion version) { + if (version is null) + { + throw new System.ArgumentNullException(nameof(version)); + } + return Major == version.Major; } diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttributePair.cs similarity index 66% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs rename to src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttributePair.cs index 8a69fd4..64d865f 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttribute.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttributePair.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK; -public class FrostFsAttribute(string key, string value) +public class FrostFsAttributePair(string key, string value) { public string Key { get; set; } = key; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs index 5b9e349..f43c6d0 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs @@ -1,13 +1,21 @@ -namespace FrostFS.SDK; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace FrostFS.SDK; public class FrostFsLinkObject : FrostFsObject { - public FrostFsLinkObject(FrostFsContainerId containerId, SplitId splitId, FrostFsObjectHeader largeObjectHeader) + public FrostFsLinkObject(FrostFsContainerId containerId, + SplitId splitId, + FrostFsObjectHeader largeObjectHeader, + IList children) : base(containerId) { - Header!.Split = new FrostFsSplit(splitId) - { - ParentHeader = largeObjectHeader - }; + Header!.Split = new FrostFsSplit(splitId, + null, + null, + largeObjectHeader, + null, + new ReadOnlyCollection(children)); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs index 5ee8b8e..a6c82db 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs @@ -1,9 +1,11 @@ -using System; +using FrostFS.SDK.ClientV2; namespace FrostFS.SDK; public class FrostFsObject { + private byte[]? bytes; + /// /// Creates new instance from ObjectHeader /// @@ -37,19 +39,27 @@ public class FrostFsObject get; set; } - /// - /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings - /// Used only for PutSingleObject method - /// - /// Buffer for output data - public byte[] Payload { get; set; } = []; - /// /// A payload is obtained via stream reader /// /// Reader for received data public IObjectReader? ObjectReader { get; set; } + internal byte[] SingleObjectPayload + { + get { return bytes ?? []; } + } + + /// + /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings + /// Used only for PutSingleObject method + /// + /// Buffer for output data + public void SetSingleObjectPayload(byte[] bytes) + { + this.bytes = bytes; + } + /// /// Applied only for the last Object in chain in case of manual multipart uploading /// @@ -57,7 +67,7 @@ public class FrostFsObject public void SetParent(FrostFsObjectHeader largeObjectHeader) { if (Header?.Split == null) - throw new Exception("The object is not initialized properly"); + throw new InvalidObjectException("The object is not initialized properly"); Header.Split.ParentHeader = largeObjectHeader; } diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs index feae9bd..f2f21d7 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs @@ -27,7 +27,7 @@ public abstract class FrostFsObjectFilter(FrostFsMatchType matchType, string /// Match type /// Attribute key /// Attribute value -public class FilterByAttribute(FrostFsMatchType matchType, string key, string value) : FrostFsObjectFilter(matchType, key, value) { } +public class FilterByAttributePair(FrostFsMatchType matchType, string key, string value) : FrostFsObjectFilter(matchType, key, value) { } /// /// Creates filter to search by ObjectId diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs index e384ece..ad8887f 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using FrostFS.Object; @@ -11,15 +11,17 @@ namespace FrostFS.SDK; public class FrostFsObjectHeader( FrostFsContainerId containerId, FrostFsObjectType type = FrostFsObjectType.Regular, - FrostFsAttribute[]? attributes = null, + FrostFsAttributePair[]? attributes = null, FrostFsSplit? split = null, FrostFsOwner? owner = null, FrostFsVersion? version = null) { - private Header header; + private Header? header; private Container.Container.Types.Attribute[]? grpsAttributes; - - public List Attributes { get; internal set; } = attributes != null ? [.. attributes] : []; + + public ReadOnlyCollection? Attributes { get; internal set; } = + attributes == null ? null : + new ReadOnlyCollection(attributes); public FrostFsContainerId ContainerId { get; } = containerId; @@ -65,9 +67,12 @@ public class FrostFsObjectHeader( PayloadLength = PayloadLength }; - foreach (var attribute in Attributes) + if (Attributes != null) { - this.header.Attributes.Add(attribute.ToMessage()); + foreach (var attribute in Attributes) + { + this.header.Attributes.Add(attribute.ToMessage()); + } } var split = Split; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs index 4e4dbf0..49fde5a 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs @@ -10,6 +10,11 @@ public class FrostFsObjectId(string id) public static FrostFsObjectId FromHash(byte[] hash) { + if (hash is null) + { + throw new ArgumentNullException(nameof(hash)); + } + if (hash.Length != Constants.Sha256HashLength) throw new FormatException("ObjectID must be a sha256 hash."); diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs index d249d80..0a24164 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs @@ -8,7 +8,7 @@ namespace FrostFS.SDK; public class FrostFsOwner(string id) { - private OwnerID ownerID; + private OwnerID? ownerID; public string Value { get; } = id; @@ -17,9 +17,9 @@ public class FrostFsOwner(string id) return new FrostFsOwner(key.PublicKey().PublicKeyToAddress()); } - internal OwnerID OwnerID + internal OwnerID OwnerID { - get + get { ownerID ??= this.ToMessage(); return ownerID; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs index 947662f..64e68e2 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs @@ -1,8 +1,13 @@ -using System.Collections.Generic; +using System.Collections.ObjectModel; namespace FrostFS.SDK; -public class FrostFsSplit(SplitId splitId) +public class FrostFsSplit(SplitId splitId, + FrostFsObjectId? previous = null, + FrostFsObjectId? parent = null, + FrostFsObjectHeader? parentHeader = null, + FrostFsSignature? parentSignature = null, + ReadOnlyCollection? children = null) { public FrostFsSplit() : this(new SplitId()) { @@ -10,15 +15,14 @@ public class FrostFsSplit(SplitId splitId) public SplitId SplitId { get; private set; } = splitId; - public FrostFsObjectId? Parent { get; set; } + public FrostFsObjectId? Previous { get; } = previous; - public FrostFsObjectId? Previous { get; set; } + public FrostFsObjectId? Parent { get; } = parent; - public FrostFsSignature? ParentSignature { get; set; } + public FrostFsSignature? ParentSignature { get; } = parentSignature; - public FrostFsObjectHeader? ParentHeader { get; set; } + public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader; - public List Children { get; } = []; + public ReadOnlyCollection? Children { get; } = children; - public Refs.Signature ParentSignatureGrpc { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs b/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs index bb8e617..177817e 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs @@ -1,35 +1,35 @@ -using FrostFS.SDK.Cryptography; +using System; + +using FrostFS.SDK.Cryptography; using Google.Protobuf; -using System; - namespace FrostFS.SDK; public class SplitId { private readonly Guid id; - private ByteString? _message; + private ByteString? message; public SplitId() { - id = Guid.NewGuid(); + this.id = Guid.NewGuid(); } - public SplitId(Guid guid) + public SplitId(Guid id) { - id = guid; + this.id = id; } private SplitId(byte[] binary) { - id = new Guid(binary); + this.id = new Guid(binary); } private SplitId(string str) { - id = new Guid(str); + this.id = new Guid(str); } public static SplitId CreateFromBinary(byte[] binaryData) @@ -44,19 +44,19 @@ public class SplitId public override string ToString() { - return id.ToString(); + return this.id.ToString(); } public byte[]? ToBinary() { - if (id == Guid.Empty) + if (this.id == Guid.Empty) return null; - return id.ToBytes(); + return this.id.ToBytes(); } public ByteString? GetSplitId() { - return _message ??= ByteString.CopyFrom(ToBinary()); + return this.message ??= ByteString.CopyFrom(ToBinary()); } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs index d7ca6b6..995b161 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK; -public class FrostFsSignature +public class FrostFsSignature() { public byte[]? Key { get; set; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs index f1704a9..0e96929 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/Context.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Security.Cryptography; using System.Threading; @@ -13,32 +13,32 @@ namespace FrostFS.SDK.ClientV2; public class Context() { - private List? interceptors; + private ReadOnlyCollection? interceptors; private ByteString? publicKeyCache; - public ECDsa Key { get; set; } + public ECDsa? Key { get; set; } - public FrostFsOwner OwnerId { get; set; } + public FrostFsOwner? OwnerId { get; set; } - public FrostFsVersion Version { get; set; } + public FrostFsVersion? Version { get; set; } + + public CancellationToken CancellationToken { get; set; } + + public TimeSpan Timeout { get; set; } - public CancellationToken CancellationToken { get; set; } = default; - - public TimeSpan Timeout { get; set; } = default; - public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; public Action? Callback { get; set; } - public List Interceptors + public ReadOnlyCollection? Interceptors { - get { return this.interceptors ??= []; } + get { return this.interceptors; } set { this.interceptors = value; } } public ByteString? GetPublicKeyCache() - { + { if (publicKeyCache == null && Key != null) { publicKeyCache = ByteString.CopyFrom(Key.PublicKey()); diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs index 1aedefe..2a6e542 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs @@ -2,12 +2,12 @@ namespace FrostFS.SDK.ClientV2; -public class PrmBase() : IContext +public class PrmBase(NameValueCollection? xheaders = null) : IContext { /// /// FrostFS request X-Headers /// - public NameValueCollection XHeaders { get; set; } = []; + public NameValueCollection XHeaders { get; } = xheaders ?? []; /// public Context? Context { get; set; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index 01f87a8..da4c50b 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -9,7 +9,7 @@ public sealed class PrmContainerCreate(FrostFsContainerInfo container) : PrmBase /// /// Rules for polling the result public PrmWait? WaitParams { get; set; } - + /// /// Blank session token /// diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index f129109..6031ca6 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; public sealed class PrmNetmapSnapshot() : PrmBase -{ +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs index 3e82ff5..f014f14 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; public sealed class PrmNetworkSettings() : PrmBase -{ +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs index 7a3d1dc..05b541d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; public sealed class PrmNodeInfo() : PrmBase -{ +{ } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index 67919aa..40410f3 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -3,7 +3,7 @@ public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken { public FrostFsContainerId ContainerId { get; set; } = containerId; - + public FrostFsObjectId ObjectId { get; set; } = objectId; /// diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index 0be09e8..3d216d5 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -28,7 +28,7 @@ public sealed class PrmObjectPut : PrmBase, ISessionToken /// Overrides default size of the buffer for stream transferring. /// /// Size of the buffer - public int BufferMaxSize { get; set; } + public int BufferMaxSize { get; set; } /// /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. @@ -40,7 +40,7 @@ public sealed class PrmObjectPut : PrmBase, ISessionToken internal int MaxObjectSizeCache { get; set; } - internal ulong CurrentStreamPosition { get; set; } = 0; + internal ulong CurrentStreamPosition { get; set; } - internal ulong FullLength { get; set; } = 0; + internal ulong FullLength { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 1e0ef9b..c42b2ba 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -3,22 +3,22 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Threading.Tasks; -using FrostFS.SDK.ClientV2; using FrostFS.Container; +using FrostFS.Refs; +using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.Refs; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -internal class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider +internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider { readonly SessionProvider sessions = new(context); public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) { - return await sessions.GetOrCreateSession(args, ctx); + return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } internal async Task GetContainerAsync(PrmContainerGet args) @@ -35,22 +35,29 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; + ctx.OwnerId ??= Context.Owner; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + if (ctx.OwnerId == null) + throw new InvalidObjectException(nameof(ctx.OwnerId)); var request = new ListRequest { - Body = new () + Body = new() { - OwnerId = ctx.OwnerId.ToMessage() + OwnerId = ctx.OwnerId.ToMessage() } }; - + request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); - + Verifier.CheckResponse(response); - + foreach (var cid in response.Body.ContainerIds) { yield return new FrostFsContainerId(Base58.Encode(cid.Value.ToByteArray())); @@ -63,42 +70,52 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient var grpcContainer = args.Container.GetContainer(); - grpcContainer.OwnerId ??= ctx.OwnerId.ToMessage(); - grpcContainer.Version ??= ctx.Version.ToMessage(); + grpcContainer.OwnerId ??= ctx.OwnerId?.ToMessage(); + grpcContainer.Version ??= ctx.Version?.ToMessage(); + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + if (grpcContainer.OwnerId == null) + throw new InvalidObjectException(nameof(grpcContainer.OwnerId)); + if (grpcContainer.Version == null) + throw new InvalidObjectException(nameof(grpcContainer.Version)); var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = ctx.Key.SignRFC6979(grpcContainer) + Signature = ctx.Key.SignRFC6979(grpcContainer) } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateContainerTokenContext( null, ContainerSessionContext.Types.Verb.Put, ctx.Key, - ctx.GetPublicKeyCache()); + ctx.GetPublicKeyCache()!); request.AddMetaHeader(args.XHeaders, sessionToken); request.Sign(ctx.Key); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); - + Verifier.CheckResponse(response); - await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx); - - return new FrostFsContainerId(response.Body.ContainerId); + await WaitForContainer(WaitExpects.Exists, response.Body.ContainerId, args.WaitParams, ctx).ConfigureAwait(false); + + return new FrostFsContainerId(response.Body.ContainerId); } internal async Task DeleteContainerAsync(PrmContainerDelete args) { var ctx = args.Context!; + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new DeleteRequest { Body = new DeleteRequest.Types.Body @@ -108,13 +125,13 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateContainerTokenContext( request.Body.ContainerId, ContainerSessionContext.Types.Verb.Delete, ctx.Key, - ctx.GetPublicKeyCache()); + ctx.GetPublicKeyCache()!); request.AddMetaHeader(args.XHeaders, sessionToken); @@ -124,13 +141,17 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient Verifier.CheckResponse(response); - await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx); - + await WaitForContainer(WaitExpects.Removed, request.Body.ContainerId, args.WaitParams, ctx) + .ConfigureAwait(false); + Verifier.CheckResponse(response); } private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx) { + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new GetRequest { Body = new GetRequest.Types.Body @@ -146,7 +167,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient } private enum WaitExpects - { + { Exists, Removed } @@ -161,7 +182,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient Verifier.CheckResponse(response); } - await WaitFor(action, expect, waitParams); + await WaitFor(action, expect, waitParams).ConfigureAwait(false); } private static async Task WaitFor( @@ -176,7 +197,7 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient { try { - await action(); + await action().ConfigureAwait(false); if (expect == WaitExpects.Exists) return; @@ -184,20 +205,20 @@ internal class ContainerServiceProvider(ContainerService.ContainerServiceClient if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); - await Task.Delay(waitParams.PollInterval); + await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } catch (ResponseException ex) { if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); - if (ex.Status.Code != FrostFsStatusCode.ContainerNotFound) + if (ex.Status?.Code != FrostFsStatusCode.ContainerNotFound) throw; if (expect == WaitExpects.Removed) return; - await Task.Delay(waitParams.PollInterval); + await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } } } diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index d932dfb..91cca73 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -8,12 +8,12 @@ using static FrostFS.Netmap.NetworkConfig.Types; namespace FrostFS.SDK.ClientV2; -internal class NetmapServiceProvider : ContextAccessor -{ +internal sealed class NetmapServiceProvider : ContextAccessor +{ private readonly NetmapService.NetmapServiceClient netmapServiceClient; - - internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context) - : base(context) + + internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context) + : base(context) { this.netmapServiceClient = netmapServiceClient; } @@ -23,7 +23,7 @@ internal class NetmapServiceProvider : ContextAccessor if (Context.NetworkSettings != null) return Context.NetworkSettings; - var info = await GetNetworkInfoAsync(ctx); + var info = await GetNetworkInfoAsync(ctx).ConfigureAwait(false); var settings = new NetworkSettings(); @@ -33,13 +33,18 @@ internal class NetmapServiceProvider : ContextAccessor } Context.NetworkSettings = settings; - + return settings; } internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new LocalNodeInfoRequest { Body = new LocalNodeInfoRequest.Types.Body { } @@ -47,7 +52,7 @@ internal class NetmapServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); - + var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -57,12 +62,18 @@ internal class NetmapServiceProvider : ContextAccessor internal async Task GetNetworkInfoAsync(Context ctx) { - var request = new NetworkInfoRequest(); + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + + var request = new NetworkInfoRequest(); request.AddMetaHeader(null); request.Sign(ctx.Key); - - var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken) + .ConfigureAwait(false); Verifier.CheckResponse(response); @@ -72,12 +83,16 @@ internal class NetmapServiceProvider : ContextAccessor internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); var request = new NetmapSnapshotRequest(); request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); - + var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -95,12 +110,12 @@ internal class NetmapServiceProvider : ContextAccessor ulong val = 0; for (var i = bytes.Length - 1; i >= 0; i--) val = (val << 8) + bytes[i]; - + return val; } private static void SetNetworksParam(Parameter param, NetworkSettings settings) - { + { var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); var valueBytes = param.Value.ToByteArray(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index bf8acf2..dce9787 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -6,9 +6,8 @@ using System.Threading.Tasks; using FrostFS.Object; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Extensions; -using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; @@ -16,19 +15,30 @@ using Google.Protobuf; namespace FrostFS.SDK.ClientV2; -internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) - : ContextAccessor(env), ISessionProvider +internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider { - readonly SessionProvider sessions = new (env); + private readonly SessionProvider sessions; + private ObjectService.ObjectServiceClient client; - public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) + : base(env) { - return await sessions.GetOrCreateSession(args, ctx); + this.sessions = new(Context); + this.client = client; + } + + public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + { + return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); var request = new HeadRequest { @@ -42,7 +52,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( request.Body.Address, @@ -53,8 +63,8 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(ctx.Key); - var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken); - + var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); + Verifier.CheckResponse(response); return response.Body.Header.Header.ToModel(); @@ -63,7 +73,12 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C internal async Task GetObjectAsync(PrmObjectGet args) { var ctx = args.Context!; - + + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new GetRequest { Body = new GetRequest.Types.Body @@ -76,7 +91,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( request.Body.Address, @@ -87,12 +102,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(ctx.Key); - return await GetObject(request, ctx); + return await GetObject(request, ctx).ConfigureAwait(false); } internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new DeleteRequest { Body = new DeleteRequest.Types.Body @@ -105,7 +125,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( request.Body.Address, @@ -116,26 +136,29 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(ctx.Key); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); - + Verifier.CheckResponse(response); } internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { var ctx = args.Context!; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var request = new SearchRequest { Body = new SearchRequest.Types.Body { ContainerId = args.ContainerId.ToMessage(), - Filters = { }, Version = 1 // TODO: clarify this param } }; request.Body.Filters.AddRange(args.Filters.Select(f => f.ToMessage())); - - var sessionToken = await GetOrCreateSession(args, ctx); + + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( new Address { ContainerId = request.Body.ContainerId }, @@ -143,7 +166,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C ctx.Key); request.AddMetaHeader(args.XHeaders, sessionToken); - + request.Sign(ctx.Key); var objectsIds = SearchObjects(request, ctx); @@ -156,14 +179,17 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C internal async Task PutObjectAsync(PrmObjectPut args) { + if (args is null) + throw new ArgumentNullException(nameof(args)); + if (args.Header == null) - throw new ArgumentException("Value cannot be null", nameof(args.Header)); - + throw new ArgumentException(nameof(args.Header)); + if (args.Payload == null) - throw new ArgumentException("Value cannot be null", nameof(args.Payload)); + throw new ArgumentException(nameof(args.Payload)); if (args.ClientCut) - return await PutClientCutObject(args); + return await PutClientCutObject(args).ConfigureAwait(false); else { if (args.Header.PayloadLength > 0) @@ -171,24 +197,28 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C else if (args.Payload.CanSeek) args.FullLength = (ulong)args.Payload.Length; - return (await PutStreamObject(args)).ObjectId; + return (await PutStreamObject(args).ConfigureAwait(false)).ObjectId; } } - + internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) { var ctx = args.Context!; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx); var request = new PutSingleRequest { - Body = new () { Object = grpcObject } + Body = new() { Object = grpcObject } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( - new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId}, + new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId }, ObjectSessionContext.Types.Verb.Put, ctx.Key); @@ -196,18 +226,18 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C request.Sign(ctx.Key); - var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); Verifier.CheckResponse(response); - + return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } - + private async Task PutClientCutObject(PrmObjectPut args) { var ctx = args.Context!; - - var tokenRaw = await GetOrCreateSession(args, ctx); + + var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false); var token = new FrostFsSessionToken(tokenRaw.Serialize()); args.SessionToken = token; @@ -216,7 +246,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var header = args.Header!; var fullLength = header.PayloadLength; - + if (payloadStream.CanSeek && fullLength == 0) fullLength = (ulong)payloadStream.Length; @@ -224,12 +254,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C if (args.MaxObjectSizeCache == 0) { - var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }); + var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }) + .ConfigureAwait(false); + args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; } var restBytes = fullLength - args.CurrentStreamPosition; - var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache; + var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache; //define collection capacity var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; @@ -238,23 +270,20 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C List sentObjectIds = new(objectsCount); FrostFsSplit? split = null; + SplitId splitId = new(); // keep attributes for the large object var attributes = args.Header!.Attributes; + args.Header!.Attributes = null; // send all parts except the last one as separate Objects - while (restBytes > (ulong)args.MaxObjectSizeCache) + while (restBytes > (ulong)args.MaxObjectSizeCache) { - if (split == null) - { - split = new FrostFsSplit(); - args.Header!.Attributes = []; - } + split = new FrostFsSplit(splitId, sentObjectIds.LastOrDefault()); - split!.Previous = sentObjectIds.LastOrDefault(); args.Header!.Split = split; - var result = await PutStreamObject(args); + var result = await PutStreamObject(args).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); @@ -264,26 +293,30 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C // send the last part and create linkObject if (sentObjectIds.Count > 0) { - var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId) { PayloadLength = fullLength }; - - largeObjectHeader.Attributes.AddRange(attributes); + var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, [.. attributes]) + { + PayloadLength = fullLength, + }; args.Header.Split!.ParentHeader = largeObjectHeader; - var result = await PutStreamObject(args); + var result = await PutStreamObject(args).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); - var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader) - .AddChildren(sentObjectIds); + var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds); - _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context}); + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context }).ConfigureAwait(false); - return split.Parent!; + var parentHeader = args.Header.GetHeader(); + + return parentHeader.Split!.Parent.ToModel(); } // We are here if the payload is placed to one Object. It means no cut action, just simple PUT. - var singlePartResult = await PutStreamObject(args); + args.Header!.Attributes = attributes; + + var singlePartResult = await PutStreamObject(args).ConfigureAwait(false); return singlePartResult.ObjectId; } @@ -297,6 +330,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C private async Task PutStreamObject(PrmObjectPut args) { var ctx = args.Context!; + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + var payload = args.Payload!; var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; @@ -307,7 +343,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C bool isRentBuffer = false; byte[]? chunkBuffer = null; - + try { if (args.CustomBuffer != null) @@ -316,7 +352,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } else { - chunkBuffer = env.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + chunkBuffer = Context.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); isRentBuffer = true; } @@ -325,7 +361,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C // 0 means no limit from client, so server side cut is performed var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; - var stream = await GetUploadStream(args, ctx); + using var stream = await GetUploadStream(args, ctx).ConfigureAwait(false); while (objectLimitSize == 0 || sentBytes < objectLimitSize) { @@ -334,7 +370,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C (int)Math.Min(objectLimitSize - sentBytes, chunkSize) : chunkSize; - var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken); + var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken).ConfigureAwait(false); if (bytesCount == 0) break; @@ -351,10 +387,10 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C chunkRequest.Sign(ctx.Key); - await stream.Write(chunkRequest); + await stream.Write(chunkRequest).ConfigureAwait(false); } - var response = await stream.Close(); + var response = await stream.Close().ConfigureAwait(false); Verifier.CheckResponse(response); return new PutObjectResult(FrostFsObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); @@ -372,6 +408,9 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C { var header = args.Header!; + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + header.OwnerId ??= ctx.OwnerId; header.Version ??= ctx.Version; @@ -395,7 +434,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } }; - var sessionToken = await GetOrCreateSession(args, ctx); + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid }, @@ -407,19 +446,19 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C initRequest.Sign(ctx.Key); - return await PutObjectInit(initRequest, ctx); + return await PutObjectInit(initRequest, ctx).ConfigureAwait(false); } private async Task GetObject(GetRequest request, Context ctx) { var reader = GetObjectInit(request, ctx); - - var grpcObject = await reader.ReadHeader(); + + var grpcObject = await reader.ReadHeader().ConfigureAwait(false); var modelObject = grpcObject.ToModel(); - + modelObject.ObjectReader = reader; - return modelObject; + return modelObject; } private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) @@ -428,7 +467,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C throw new ArgumentNullException(nameof(initRequest)); var call = client.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); - + return new ObjectReader(call); } @@ -441,7 +480,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); - await call.RequestStream.WriteAsync(initRequest); + await call.RequestStream.WriteAsync(initRequest).ConfigureAwait(false); return new ObjectStreamer(call); } @@ -449,14 +488,14 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C private async IAsyncEnumerable SearchObjects(SearchRequest request, Context ctx) { using var stream = GetSearchReader(request, ctx); - + while (true) { - var ids = await stream.Read(ctx.CancellationToken); + var ids = await stream.Read(ctx.CancellationToken).ConfigureAwait(false); if (ids == null) break; - + foreach (var oid in ids) { yield return oid; @@ -472,7 +511,7 @@ internal class ObjectServiceProvider(ObjectService.ObjectServiceClient client, C } var call = client.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); - + return new SearchReader(call); - } + } } diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 4498b3c..de22165 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -1,17 +1,17 @@ using System.Threading.Tasks; -using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -internal class SessionServiceProvider : ContextAccessor -{ +internal sealed class SessionServiceProvider : ContextAccessor +{ private readonly SessionService.SessionServiceClient? _sessionServiceClient; internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context) - : base (context) + : base(context) { _sessionServiceClient = sessionServiceClient; } @@ -19,27 +19,30 @@ internal class SessionServiceProvider : ContextAccessor internal async Task CreateSessionAsync(PrmSessionCreate args) { var ctx = args.Context!; + + ctx.OwnerId ??= Context.Owner; + var request = new CreateRequest { Body = new CreateRequest.Types.Body { - OwnerId = ctx.OwnerId.ToMessage(), + OwnerId = ctx.OwnerId!.ToMessage(), Expiration = args.Expiration } }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ctx.Key!); - return await CreateSession(request, args.Context!); + return await CreateSession(request, args.Context!).ConfigureAwait(false); } internal async Task CreateSession(CreateRequest request, Context ctx) { var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); - + Verifier.CheckResponse(response); - + return new SessionToken { Body = new SessionToken.Types.Body diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs index 596d79e..ea92329 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs @@ -5,12 +5,12 @@ using Frostfs.V2.Apemanager; namespace FrostFS.SDK.ClientV2; -internal class ApeManagerServiceProvider : ContextAccessor -{ +internal sealed class ApeManagerServiceProvider : ContextAccessor +{ private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient; internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientEnvironment context) - : base (context) + : base(context) { _apeManagerServiceClient = apeManagerServiceClient; } @@ -18,12 +18,16 @@ internal class ApeManagerServiceProvider : ContextAccessor internal async Task AddChainAsync(PrmApeChainAdd args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); AddChainRequest request = new() { Body = new() { - Chain = new () { Raw = args.Chain.GetRaw() }, + Chain = new() { Raw = args.Chain.GetRaw() }, Target = args.Target.GetChainTarget() } }; @@ -41,6 +45,11 @@ internal class ApeManagerServiceProvider : ContextAccessor internal async Task RemoveChainAsync(PrmApeChainRemove args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + RemoveChainRequest request = new() { Body = new() @@ -61,6 +70,11 @@ internal class ApeManagerServiceProvider : ContextAccessor internal async Task ListChainAsync(PrmApeChainList args) { var ctx = args.Context!; + ctx.Key ??= Context.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + ListChainsRequest request = new() { Body = new() diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index 9e5b142..8d0d1f5 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -7,13 +7,14 @@ internal interface ISessionProvider ValueTask GetOrCreateSession(ISessionToken args, Context ctx); } -internal class SessionProvider(ClientEnvironment env) -{ +internal sealed class SessionProvider(ClientEnvironment env) +{ public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) { if (args.SessionToken is null) - { - return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }); + { + return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }) + .ConfigureAwait(false); } return new Session.SessionToken().Deserialize(args.SessionToken.Token); diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs index 1ac5183..11023e7 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs @@ -6,9 +6,9 @@ using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(Client client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable +public class ClientEnvironment(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { - private ArrayPool _arrayPool; + private ArrayPool? _arrayPool; internal FrostFsOwner? Owner { get; } = owner; @@ -18,7 +18,7 @@ public class ClientEnvironment(Client client, ECDsa? key, FrostFsOwner? owner, G internal NetworkSettings? NetworkSettings { get; set; } - internal Client Client { get; } = client; + internal FrostFSClient Client { get; } = client; internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null; @@ -28,7 +28,7 @@ public class ClientEnvironment(Client client, ECDsa? key, FrostFsOwner? owner, G internal ArrayPool GetArrayPool(int size) { _arrayPool ??= ArrayPool.Create(size, 256); - + return _arrayPool; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Object.cs b/src/FrostFS.SDK.ClientV2/Tools/Object.cs deleted file mode 100644 index 05d957c..0000000 --- a/src/FrostFS.SDK.ClientV2/Tools/Object.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace FrostFS.SDK.ClientV2.Extensions; - -public static class ObjectExtensions -{ - public static FrostFsObject SetPayloadLength(this FrostFsObject obj, ulong length) - { - obj.Header.PayloadLength = length; - return obj; - } - - public static FrostFsObject SetPayload(this FrostFsObject obj, byte[] bytes) - { - obj.Payload = bytes; - return obj; - } - - public static FrostFsObject AddAttribute(this FrostFsObject obj, string key, string value) - { - obj.AddAttribute(new FrostFsAttribute(key, value)); - return obj; - } - - public static FrostFsObject AddAttribute(this FrostFsObject obj, FrostFsAttribute attribute) - { - obj.Header.Attributes.Add(attribute); - return obj; - } - - public static FrostFsObject AddAttributes(this FrostFsObject obj, IEnumerable attributes) - { - obj.Header.Attributes.AddRange(attributes); - return obj; - } - - public static FrostFsObject SetSplit(this FrostFsObject obj, FrostFsSplit? split) - { - obj.Header.Split = split; - return obj; - } - - public static FrostFsLinkObject AddChildren(this FrostFsLinkObject linkObject, IEnumerable objectIds) - { - linkObject.Header.Split!.Children.AddRange(objectIds); - return linkObject; - } - - public static FrostFsObject CalculateObjectId(this FrostFsObject obj) - { - if (obj.Header == null) - throw new MissingFieldException("Header cannot be null"); - - return obj; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs index 07c4793..b1ad4f7 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs @@ -1,30 +1,30 @@ using System; +using System.Threading; using System.Threading.Tasks; +using FrostFS.Object; + using Grpc.Core; -using FrostFS.Object; -using System.Threading; - namespace FrostFS.SDK.ClientV2; -public class ObjectReader(AsyncServerStreamingCall call) : IObjectReader +public sealed class ObjectReader(AsyncServerStreamingCall call) : IObjectReader { - private bool disposed = false; + private bool disposed; public AsyncServerStreamingCall Call { get; private set; } = call; internal async Task ReadHeader() { - if (!await Call.ResponseStream.MoveNext()) + if (!await Call.ResponseStream.MoveNext().ConfigureAwait(false)) throw new InvalidOperationException("unexpected end of stream"); - + var response = Call.ResponseStream.Current; Verifier.CheckResponse(response); - + if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init) throw new InvalidOperationException("unexpected message type"); - + return new Object.Object { ObjectId = response.Body.Init.ObjectId, @@ -34,12 +34,12 @@ public class ObjectReader(AsyncServerStreamingCall call) : IObjectR public async Task?> ReadChunk(CancellationToken cancellationToken = default) { - if (!await Call.ResponseStream.MoveNext(cancellationToken)) + if (!await Call.ResponseStream.MoveNext(cancellationToken).ConfigureAwait(false)) return null; - + var response = Call.ResponseStream.Current; Verifier.CheckResponse(response); - + if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk) throw new InvalidOperationException("unexpected message type"); diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs index 9350282..acd0d28 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs @@ -1,13 +1,13 @@ using System; using System.Threading.Tasks; -using Grpc.Core; - using FrostFS.Object; +using Grpc.Core; + namespace FrostFS.SDK.ClientV2; -internal class ObjectStreamer(AsyncClientStreamingCall call) : IDisposable +internal sealed class ObjectStreamer(AsyncClientStreamingCall call) : IDisposable { public AsyncClientStreamingCall Call { get; private set; } = call; @@ -18,14 +18,14 @@ internal class ObjectStreamer(AsyncClientStreamingCall throw new ArgumentNullException(nameof(request)); } - await Call.RequestStream.WriteAsync(request); + await Call.RequestStream.WriteAsync(request).ConfigureAwait(false); } public async Task Close() { - await Call.RequestStream.CompleteAsync(); - - return await Call.ResponseAsync; + await Call.RequestStream.CompleteAsync().ConfigureAwait(false); + + return await Call.ResponseAsync.ConfigureAwait(false); } public void Dispose() diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index 64c09cd..0303626 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -1,35 +1,35 @@ using System.Linq; -using Google.Protobuf; - using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; +using Google.Protobuf; + namespace FrostFS.SDK.ClientV2; -internal class ObjectTools +internal static class ObjectTools { internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx) - { + { var grpcHeader = CreateHeader(header, [], ctx); if (header.Split != null) SetSplitValues(grpcHeader, header.Split, ctx); - return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal static Object.Object CreateObject(FrostFsObject @object, Context ctx) + internal static Object.Object CreateObject(FrostFsObject @object, Context ctx) { @object.Header.OwnerId ??= ctx.OwnerId; @object.Header.Version ??= ctx.Version; var grpcHeader = @object.Header.GetHeader(); - - grpcHeader.PayloadLength = (ulong)@object.Payload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.Payload); + + grpcHeader.PayloadLength = (ulong)@object.SingleObjectPayload.Length; + grpcHeader.PayloadHash = Sha256Checksum(@object.SingleObjectPayload); var split = @object.Header.Split; if (split != null) @@ -41,15 +41,15 @@ internal class ObjectTools { Header = grpcHeader, ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, - Payload = ByteString.CopyFrom(@object.Payload) + Payload = ByteString.CopyFrom(@object.SingleObjectPayload) }; - obj.Signature = new Refs.Signature + obj.Signature = new Signature { Key = ctx.GetPublicKeyCache(), - Sign = ByteString.CopyFrom(ctx.Key.SignData(obj.ObjectId.ToByteArray())), - }; - + Sign = ByteString.CopyFrom(ctx.Key!.SignData(obj.ObjectId.ToByteArray())), + }; + return obj; } @@ -58,6 +58,9 @@ internal class ObjectTools if (split == null) return; + if (ctx.Key == null) + throw new InvalidObjectException(nameof(ctx.Key)); + grpcHeader.Split = new Header.Types.Split { SplitId = split.SplitId?.GetSplitId() @@ -72,13 +75,11 @@ internal class ObjectTools grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; grpcHeader.Split.ParentHeader = grpcParentHeader; - grpcHeader.Split.ParentSignature = new Refs.Signature + grpcHeader.Split.ParentSignature = new Signature { Key = ctx.GetPublicKeyCache(), Sign = ByteString.CopyFrom(ctx.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), }; - - split.Parent = grpcHeader.Split.Parent.ToModel(); } grpcHeader.Split.Previous = split.Previous?.ToMessage(); @@ -90,10 +91,10 @@ internal class ObjectTools header.Version ??= ctx.Version; var grpcHeader = header.GetHeader(); - + if (payload != null) // && payload.Length > 0 grpcHeader.PayloadHash = Sha256Checksum(payload); - + return grpcHeader; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/Range.cs b/src/FrostFS.SDK.ClientV2/Tools/Range.cs index eae7b50..8add54e 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Range.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Range.cs @@ -25,7 +25,7 @@ namespace System { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - + if (fromEnd) _value = ~value; else @@ -51,7 +51,7 @@ namespace System { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - + return new Index(value); } @@ -62,7 +62,7 @@ namespace System { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - + return new Index(~value); } @@ -119,9 +119,9 @@ namespace System public override string ToString() { if (IsFromEnd) - return "^" + ((uint)Value).ToString(); + return $"^{(uint)Value}"; - return ((uint)Value).ToString(); + return $"{(uint)Value}"; } } @@ -189,7 +189,7 @@ namespace System { int start; var startIndex = Start; - + if (startIndex.IsFromEnd) start = length - startIndex.Value; else @@ -205,7 +205,7 @@ namespace System if ((uint)end > (uint)length || (uint)start > (uint)end) throw new ArgumentOutOfRangeException(nameof(length)); - + return (start, end - start); } } @@ -222,7 +222,7 @@ namespace System.Runtime.CompilerServices { if (array == null) throw new ArgumentNullException(nameof(array)); - + (int offset, int length) = range.GetOffsetAndLength(array.Length); if (default(T) != null || typeof(T[]) == array.GetType()) @@ -231,7 +231,7 @@ namespace System.Runtime.CompilerServices if (length == 0) return []; - + var dest = new T[length]; Array.Copy(array, offset, dest, 0, length); return dest; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs index c12d280..6f19ad0 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Specialized; using System.Linq; using System.Security.Cryptography; @@ -12,9 +13,14 @@ namespace FrostFS.SDK.ClientV2; public static class RequestConstructor { - public static void AddMetaHeader(this IRequest request, NameValueCollection? xHeaders, Session.SessionToken? sessionToken = null) + public static void AddMetaHeader(this IRequest request, + NameValueCollection? xHeaders, + SessionToken? sessionToken = null) { - if (request.MetaHeader is not null) + if (request is null) + throw new ArgumentNullException(nameof(request)); + + if (request.MetaHeader is not null) return; request.MetaHeader = MetaHeader.Default().ToMessage(); @@ -28,11 +34,21 @@ public static class RequestConstructor (k, v) => new XHeader { Key = k, Value = v })); } - public static void CreateObjectTokenContext(this Session.SessionToken sessionToken, + public static void CreateObjectTokenContext(this SessionToken sessionToken, Address address, ObjectSessionContext.Types.Verb verb, ECDsa key) { + if (sessionToken is null) + { + throw new ArgumentNullException(nameof(sessionToken)); + } + + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + if (sessionToken.Body.Object?.Target != null) return; @@ -52,7 +68,7 @@ public static class RequestConstructor sessionToken.Signature = key.SignMessagePart(sessionToken.Body); } - public static void CreateContainerTokenContext(this Session.SessionToken sessionToken, + internal static void CreateContainerTokenContext(this SessionToken sessionToken, ContainerID? containerId, ContainerSessionContext.Types.Verb verb, ECDsa key, @@ -61,7 +77,7 @@ public static class RequestConstructor if (sessionToken.Body.Container?.ContainerId != null) return; - sessionToken.Body.Container = new (){ Verb = verb }; + sessionToken.Body.Container = new() { Verb = verb }; if (containerId != null) sessionToken.Body.Container.ContainerId = containerId; @@ -69,7 +85,7 @@ public static class RequestConstructor sessionToken.Body.Container.Wildcard = true; sessionToken.Body.SessionKey = publicKey; - + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); } } diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index c2d80f7..13d87b0 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -18,35 +18,45 @@ namespace FrostFS.SDK.ClientV2; public static class RequestSigner { - public const int RFC6979SignatureSize = 64; + internal const int RFC6979SignatureSize = 64; - public static byte[] SignRFC6979(this ECDsa key, byte[] data) + internal static byte[] SignRFC6979(this ECDsa key, byte[] data) { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (data is null) + { + throw new ArgumentNullException(nameof(data)); + } + var digest = new Sha256Digest(); var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); var privateKey = new ECPrivateKeyParameters(new BigInteger(1, key.PrivateKey()), ecParameters); var signer = new ECDsaSigner(new HMacDsaKCalculator(digest)); var hash = new byte[digest.GetDigestSize()]; - + digest.BlockUpdate(data, 0, data.Length); digest.DoFinal(hash, 0); signer.Init(true, privateKey); - + var rs = signer.GenerateSignature(hash); var signature = new byte[RFC6979SignatureSize]; var rbytes = rs[0].ToByteArrayUnsigned(); var sbytes = rs[1].ToByteArrayUnsigned(); var index = RFC6979SignatureSize / 2 - rbytes.Length; - + rbytes.CopyTo(signature, index); index = RFC6979SignatureSize - sbytes.Length; sbytes.CopyTo(signature, index); - + return signature; } - public static SignatureRFC6979 SignRFC6979(this ECDsa key, IMessage message) + internal static SignatureRFC6979 SignRFC6979(this ECDsa key, IMessage message) { return new SignatureRFC6979 { @@ -54,8 +64,8 @@ public static class RequestSigner Sign = ByteString.CopyFrom(key.SignRFC6979(message.ToByteArray())), }; } - - public static SignatureRFC6979 SignRFC6979(this ECDsa key, ByteString data) + + internal static SignatureRFC6979 SignRFC6979(this ECDsa key, ByteString data) { return new SignatureRFC6979 { @@ -74,7 +84,7 @@ public static class RequestSigner return hash; } - public static Signature SignMessagePart(this ECDsa key, IMessage? data) + internal static Signature SignMessagePart(this ECDsa key, IMessage? data) { var data2Sign = data is null ? [] : data.ToByteArray(); var sig = new Signature @@ -86,7 +96,7 @@ public static class RequestSigner return sig; } - public static void Sign(this IVerifiableMessage message, ECDsa key) + internal static void Sign(this IVerifiableMessage message, ECDsa key) { var meta = message.GetMetaHeader(); IVerificationHeader verify = message switch @@ -95,17 +105,17 @@ public static class RequestSigner IResponse => new ResponseVerificationHeader(), _ => throw new InvalidOperationException("Unsupported message type") }; - + var verifyOrigin = message.GetVerificationHeader(); - + if (verifyOrigin is null) verify.BodySignature = key.SignMessagePart(message.GetBody()); - else + else verify.SetOrigin(verifyOrigin); - + verify.MetaSignature = key.SignMessagePart(meta); verify.OriginSignature = key.SignMessagePart(verifyOrigin); - + message.SetVerificationHeader(verify); } } diff --git a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs b/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs index 1bcf398..ceed0da 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs @@ -11,17 +11,17 @@ using Grpc.Core; namespace FrostFS.SDK.ClientV2; -internal class SearchReader(AsyncServerStreamingCall call) : IDisposable +internal sealed class SearchReader(AsyncServerStreamingCall call) : IDisposable { public AsyncServerStreamingCall Call { get; private set; } = call; public async Task?> Read(CancellationToken cancellationToken) { - if (!await Call.ResponseStream.MoveNext(cancellationToken)) + if (!await Call.ResponseStream.MoveNext(cancellationToken).ConfigureAwait(false)) return null; - + var response = Call.ResponseStream.Current; - + Verifier.CheckResponse(response); return response.Body?.IdList.ToList(); diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 2640e45..2896209 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -45,7 +45,7 @@ public static class Verifier var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); var bcPublicKey = new ECPublicKeyParameters(secp256R1.Curve.DecodePoint(publicKey), ecParameters); var hash = new byte[digest.GetDigestSize()]; - + digest.BlockUpdate(data, 0, data.Length); digest.DoFinal(hash, 0); signer.Init(false, bcPublicKey); @@ -55,11 +55,25 @@ public static class Verifier public static bool VerifyRFC6979(this SignatureRFC6979 signature, IMessage message) { + if (signature is null) + { + throw new ArgumentNullException(nameof(signature)); + } + return signature.Key.ToByteArray().VerifyRFC6979(message.ToByteArray(), signature.Sign.ToByteArray()); } public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig) { + if (key is null) + throw new ArgumentNullException(nameof(key)); + + if (data is null) + throw new ArgumentNullException(nameof(data)); + + if (sig is null) + throw new ArgumentNullException(nameof(sig)); + return key.VerifyHash(data.Sha512(), sig[1..]); } @@ -70,36 +84,41 @@ public static class Verifier using var key = sig.Key.ToByteArray().LoadPublicKey(); var data2Verify = data is null ? [] : data.ToByteArray(); - + return key.VerifyData(data2Verify, sig.Sign.ToByteArray()); } - public static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification) + internal static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification) { - if (!verification.MetaSignature.VerifyMessagePart(meta)) + if (!verification.MetaSignature.VerifyMessagePart(meta)) return false; - + var origin = verification.GetOrigin(); - - if (!verification.OriginSignature.VerifyMessagePart(origin)) + + if (!verification.OriginSignature.VerifyMessagePart(origin)) return false; - + if (origin is null) return verification.BodySignature.VerifyMessagePart(body); - + return verification.BodySignature is null && VerifyMatryoskaLevel(body, meta.GetOrigin(), origin); } public static bool Verify(this IVerifiableMessage message) { + if (message is null) + { + throw new ArgumentNullException(nameof(message)); + } + return VerifyMatryoskaLevel(message.GetBody(), message.GetMetaHeader(), message.GetVerificationHeader()); } - public static void CheckResponse(IResponse resp) + internal static void CheckResponse(IResponse resp) { if (!resp.Verify()) throw new FormatException($"invalid response, type={resp.GetType()}"); - + var status = resp.MetaHeader.Status.ToModel(); if (status != null && !status.IsSuccess) @@ -112,6 +131,11 @@ public static class Verifier /// Created by SDK request to gRpc proxy public static void CheckRequest(IRequest request) { + if (request is null) + { + throw new ArgumentNullException(nameof(request)); + } + if (!request.Verify()) throw new FormatException($"invalid response, type={request.GetType()}"); } diff --git a/src/FrostFS.SDK.ClientV2/Ростелеком.txt b/src/FrostFS.SDK.ClientV2/Ростелеком.txt new file mode 100644 index 0000000..d3687ed --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Ростелеком.txt @@ -0,0 +1,8 @@ +Для возврата денег потребуются документы: +1. Заполненное заявление +Форму заявления можно скачать с сайта +Шаблон заявления скачайте на сайте rt.ru. Внизу страницы перейдите в Договоры и соглашения -> «Бланки и заявления» +2. Скан-фото паспорта владельца договора (2, 3, 5 страница: кем и когда выдан, адрес регистрации) +3. Реквизиты счета владельца, выданные банком, на которые будет выполнен возврат +4. Чек об оплате +Сканы документов должны быть хорошо читаемые diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 95b07f0..1b8f084 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -13,17 +13,17 @@ public static class Base58 { if (input is null) throw new ArgumentNullException(nameof(input)); - + byte[] buffer = Decode(input); - if (buffer.Length < 4) + if (buffer.Length < 4) throw new FormatException(); - + byte[] checksum = buffer[0..(buffer.Length - 4)].Sha256().Sha256(); if (!buffer.AsSpan(buffer.Length - 4).SequenceEqual(checksum[..4].AsSpan())) throw new FormatException(); - + var ret = buffer[..^4]; Array.Clear(buffer, 0, buffer.Length); return ret; @@ -34,7 +34,7 @@ public static class Base58 byte[] checksum = data.ToArray().Sha256().Sha256(); Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); - + checksum[..4].AsSpan().CopyTo(buffer[data.Length..]); var ret = Encode(buffer); buffer.Clear(); @@ -44,6 +44,9 @@ public static class Base58 public static byte[] Decode(string input) { + if (input == null) + throw new ArgumentNullException(nameof(input)); + // Decode Base58 string to BigInteger var bi = BigInteger.Zero; for (int i = 0; i < input.Length; i++) @@ -61,7 +64,7 @@ public static class Base58 if (bi.IsZero) return leadingZeros; - var bytesBigEndian = bi.ToByteArray().Reverse(); + var bytesBigEndian = bi.ToByteArray().Reverse().ToArray(); var firstNonZeroIndex = 0; diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index 32c8259..9d61972 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,17 +1,19 @@ -using Google.Protobuf; -using Org.BouncyCastle.Crypto.Digests; using System.Security.Cryptography; using System.Threading; +using Google.Protobuf; + +using Org.BouncyCastle.Crypto.Digests; + namespace FrostFS.SDK.Cryptography; public static class Extentions { private static readonly SHA256 _sha256 = SHA256.Create(); - private static SpinLock _spinlockSha256 = new(); + private static SpinLock _spinlockSha256; private static readonly SHA512 _sha512 = SHA512.Create(); - private static SpinLock _spinlockSha512 = new(); + private static SpinLock _spinlockSha512; internal static byte[] RIPEMD160(this byte[] value) { @@ -22,7 +24,7 @@ public static class Extentions digest.DoFinal(hash, 0); return hash; } - + public static ByteString Sha256(this IMessage data) { return ByteString.CopyFrom(data.ToByteArray().Sha256()); diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index e276848..59bde16 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -13,6 +13,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index bbffab8..0c9fd3b 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -20,6 +20,9 @@ public static class KeyExtension public static byte[] Compress(this byte[] publicKey) { + if (publicKey == null) + throw new ArgumentNullException(nameof(publicKey)); + if (publicKey.Length != UncompressedPublicKeyLength) throw new FormatException( $"{nameof(Compress)} argument isn't uncompressed public key. " + @@ -34,6 +37,9 @@ public static class KeyExtension public static byte[] Decompress(this byte[] publicKey) { + if (publicKey == null) + throw new ArgumentNullException(nameof(publicKey)); + if (publicKey.Length != CompressedPublicKeyLength) throw new FormatException( $"{nameof(Decompress)} argument isn't compressed public key. " + @@ -64,6 +70,9 @@ public static class KeyExtension public static byte[] GetScriptHash(this byte[] publicKey) { + if (publicKey == null) + throw new ArgumentNullException(nameof(publicKey)); + var script = publicKey.CreateSignatureRedeemScript(); return script.Sha256().RIPEMD160(); } @@ -79,14 +88,14 @@ public static class KeyExtension private static byte[] GetPrivateKeyFromWIF(string wif) { - if (wif == null) + if (wif == null) throw new ArgumentNullException(nameof(wif)); - + var data = wif.Base58CheckDecode(); - + if (data.Length != 34 || data[0] != 0x80 || data[33] != 0x01) throw new FormatException(); - + var privateKey = new byte[32]; Buffer.BlockCopy(data, 1, privateKey, 0, privateKey.Length); Array.Clear(data, 0, data.Length); @@ -100,6 +109,9 @@ public static class KeyExtension public static string PublicKeyToAddress(this byte[] publicKey) { + if (publicKey == null) + throw new ArgumentNullException(nameof(publicKey)); + if (publicKey.Length != CompressedPublicKeyLength) throw new FormatException( nameof(publicKey) + @@ -112,6 +124,9 @@ public static class KeyExtension public static byte[] PublicKey(this ECDsa key) { + if (key == null) + throw new ArgumentNullException(nameof(key)); + var param = key.ExportParameters(false); var pubkey = new byte[33]; var pos = 33 - param.Q.X.Length; @@ -127,6 +142,9 @@ public static class KeyExtension public static byte[] PrivateKey(this ECDsa key) { + if (key == null) + throw new ArgumentNullException(nameof(key)); + return key.ExportParameters(true).D; } @@ -153,7 +171,7 @@ public static class KeyExtension public static ECDsa LoadWif(this string wif) { var privateKey = GetPrivateKeyFromWIF(wif); - + return LoadPrivateKey(privateKey); } diff --git a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs index 5cb35a1..e8f52fc 100644 --- a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs +++ b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs @@ -28,7 +28,7 @@ internal class Murmur3_128 : HashAlgorithm length += cbSize; int remainder = cbSize & 15; int alignedLength = ibStart + (cbSize - remainder); - + for (int i = ibStart; i < alignedLength; i += 16) { ulong k1 = BinaryPrimitives.ReadUInt64LittleEndian(array.AsSpan(i)); @@ -50,7 +50,7 @@ internal class Murmur3_128 : HashAlgorithm h2 += h1; h2 = h2 * m + n2; } - + if (remainder > 0) { ulong k1 = 0, k2 = 0; @@ -101,7 +101,7 @@ internal class Murmur3_128 : HashAlgorithm h2 = Fimix64(h2); h1 += h2; h2 += h1; - + return BitConverter.GetBytes(h1); } diff --git a/src/FrostFS.SDK.Cryptography/Range.cs b/src/FrostFS.SDK.Cryptography/Range.cs index 92aa82f..495479d 100644 --- a/src/FrostFS.SDK.Cryptography/Range.cs +++ b/src/FrostFS.SDK.Cryptography/Range.cs @@ -122,9 +122,9 @@ namespace System public override string ToString() { if (IsFromEnd) - return "^" + ((uint)Value).ToString(); + return $"^{(uint)Value}"; - return ((uint)Value).ToString(); + return $"{(uint)Value}"; } } @@ -140,7 +140,7 @@ namespace System /// Construct a Range object using the start and end indexes. /// Represent the inclusive start index of the range. /// Represent the exclusive end index of the range. - internal readonly struct Range (Index start, Index end) : IEquatable + internal readonly struct Range(Index start, Index end) : IEquatable { /// Represent the inclusive start index of the Range. public Index Start { get; } = start; diff --git a/src/FrostFS.SDK.Cryptography/UUID.cs b/src/FrostFS.SDK.Cryptography/UUID.cs index fee2df9..c92a874 100644 --- a/src/FrostFS.SDK.Cryptography/UUID.cs +++ b/src/FrostFS.SDK.Cryptography/UUID.cs @@ -1,5 +1,6 @@ -using Google.Protobuf; -using System; +using System; + +using Google.Protobuf; namespace FrostFS.SDK.Cryptography; @@ -7,13 +8,16 @@ public static class UUIDExtension { public static Guid ToUuid(this ByteString id) { + if (id == null) + throw new ArgumentNullException(nameof(id)); + var bytes = id.ToByteArray(); - + var orderedBytes = GetGuidBytesDirectOrder(bytes); return new Guid(orderedBytes); } - + /// /// Serializes Guid to binary representation in direct order bytes format /// diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs index 6a637c3..32ba5d4 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs +++ b/src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs @@ -1,4 +1,5 @@ using FrostFS.Refs; + using Google.Protobuf; namespace FrostFS.Session; diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs index 1f02ea5..408622d 100644 --- a/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs @@ -1,7 +1,7 @@ -using Google.Protobuf; - -using FrostFS.Session; using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; + +using Google.Protobuf; namespace Frostfs.V2.Apemanager; diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs index 0b876f0..010072e 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs @@ -1,7 +1,7 @@ -using Google.Protobuf; - -using FrostFS.Session; using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; + +using Google.Protobuf; namespace FrostFS.Container; diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs index 2c1c8b6..1001bce 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs @@ -1,5 +1,6 @@ using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; + using Google.Protobuf; namespace FrostFS.Netmap; diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index dbfdd79..7c0c87d 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -1,6 +1,8 @@ using System.Diagnostics; + using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; + using Google.Protobuf; namespace FrostFS.Object @@ -117,7 +119,7 @@ namespace FrostFS.Object } } - public partial class PutSingleRequest : IRequest + public partial class PutSingleRequest : IRequest { IMetaHeader IVerifiableMessage.GetMetaHeader() { diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs index decf002..4f0850b 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs @@ -1,4 +1,5 @@ using FrostFS.SDK.ProtosV2.Interfaces; + using Google.Protobuf; namespace FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index fcfcb39..e58e18c 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,12 +1,12 @@ using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using Microsoft.Extensions.Options; using Google.Protobuf; +using Microsoft.Extensions.Options; + namespace FrostFS.SDK.Tests; public abstract class ContainerTestsBase @@ -34,7 +34,7 @@ public abstract class ContainerTestsBase protected IFrostFSClient GetClient() { - return ClientV2.Client.GetTestInstance( + return ClientV2.FrostFSClient.GetTestInstance( Settings, null, new NetworkMocker(this.key).GetMock().Object, @@ -45,17 +45,17 @@ public abstract class ContainerTestsBase } public class ContainerTest : ContainerTestsBase -{ +{ [Fact] public async void CreateContainerTest() { - var param = new PrmContainerCreate(new FrostFsContainerInfo(BasicAcl.PublicRW, Mocker.PlacementPolicy)); + var param = new PrmContainerCreate(new FrostFsContainerInfo(Mocker.PlacementPolicy)); var result = await GetClient().CreateContainerAsync(param); Assert.NotNull(result); - Assert.NotNull(result.Value); - Assert.True(Base58.Encode(Mocker.ContainerGuid.ToBytes()) == result.Value); + Assert.NotNull(result.GetValue()); + Assert.True(Base58.Encode(Mocker.ContainerGuid.ToBytes()) == result.GetValue()); } [Fact] @@ -63,14 +63,11 @@ public class ContainerTest : ContainerTestsBase { var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - Mocker.Acl = BasicAcl.PublicRO; - var result = await GetClient().GetContainerAsync(new PrmContainerGet(cid)); Assert.NotNull(result); - Assert.Equal(Mocker.Acl, result.BasicAcl); Assert.Equal(Mocker.ContainerGuid, result.Nonce); - Assert.Equal(0, Mocker.PlacementPolicy.CompareTo(result.PlacementPolicy)); + Assert.Equal(Mocker.PlacementPolicy, result.PlacementPolicy); Assert.Equal(Mocker.Version.ToString(), result.Version!.ToString()); } @@ -89,7 +86,7 @@ public class ContainerTest : ContainerTestsBase await foreach (var cid in result) { var val = Base58.Encode(ByteString.CopyFrom(Mocker.ContainerIds[i++]).ToByteArray()); - Assert.Equal(val, cid.Value); + Assert.Equal(val, cid.GetValue()); } Assert.Equal(3, i); @@ -106,7 +103,7 @@ public class ContainerTest : ContainerTestsBase Assert.Single(Mocker.Requests); var request = Mocker.Requests.First(); - - Assert.Equal(cid.ToMessage(), request.Request.Body.ContainerId); + + Assert.Equal(cid.ToMessage(), request.Request.Body.ContainerId); } } diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs index cc6b438..958a73e 100644 --- a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs @@ -1,7 +1,7 @@ -using Grpc.Core; -using Grpc.Core.Interceptors; +using System.Diagnostics; -using System.Diagnostics; +using Grpc.Core; +using Grpc.Core.Interceptors; namespace FrostFS.SDK.SmokeTests; @@ -21,12 +21,12 @@ public class MetricsInterceptor() : Interceptor call.GetTrailers, call.Dispose); } - + private static async Task HandleUnaryResponse(AsyncUnaryCall call) { var watch = new Stopwatch(); watch.Start(); - + var response = await call.ResponseAsync; watch.Stop(); @@ -34,6 +34,6 @@ public class MetricsInterceptor() : Interceptor // Do something with call info // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; - return response; + return response; } } diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index a8ddbbd..22172f2 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -20,7 +20,7 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) { var header = new Header { - ContainerId = objectHeader.ContainerId.ToMessage(), + ContainerId = objectHeader.ContainerId.ToMessage(), PayloadLength = objectHeader.PayloadLength, Version = objectHeader.Version!.ToMessage(), OwnerId = objectHeader.OwnerId!.ToMessage() diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs index 56746e4..9836d37 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -1,6 +1,7 @@ -using Grpc.Core; using FrostFS.SDK.ProtosV2.Interfaces; +using Grpc.Core; + namespace FrostFS.SDK.Tests; public class ClientStreamWriter : IClientStreamWriter @@ -17,13 +18,13 @@ public class ClientStreamWriter : IClientStreamWriter public Task CompleteAsync() { CompletedTask = true; - return Task.CompletedTask; + return Task.CompletedTask; } public Task WriteAsync(IRequest message) { Object.PutRequest pr = new((Object.PutRequest)message); - Messages.Add(pr); + Messages.Add(pr); return Task.CompletedTask; } } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 4235f71..ba34cde 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -1,15 +1,17 @@ using System.Security.Cryptography; -using FrostFS.Container; -using Moq; +using FrostFS.Container; +using FrostFS.Object; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; + using Google.Protobuf; -using FrostFS.SDK.ClientV2; -using FrostFS.Object; using Grpc.Core; -using FrostFS.SDK.ClientV2.Mappers.GRPC; + +using Moq; namespace FrostFS.SDK.Tests; @@ -18,11 +20,9 @@ public abstract class ServiceBase(string key) public string StringKey { get; private set; } = key; public ECDsa Key { get; private set; } = key.LoadWif(); public FrostFsVersion Version { get; set; } = DefaultVersion; - public BasicAcl Acl { get; set; } = DefaultAcl; public FrostFsPlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; public static FrostFsVersion DefaultVersion { get; } = new(2, 13); - public static BasicAcl DefaultAcl { get; } = BasicAcl.PublicRW; public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); public Metadata Metadata { get; protected set; } @@ -61,23 +61,23 @@ public abstract class ServiceBase(string key) } public ResponseMetaHeader ResponseMetaHeader => new() - { - Version = Version.ToMessage(), - Epoch = 100, - Ttl = 1 - }; + { + Version = Version.ToMessage(), + Epoch = 100, + Ttl = 1 + }; } -public abstract class ContainerServiceBase(string key) : ServiceBase (key) +public abstract class ContainerServiceBase(string key) : ServiceBase(key) { public Guid ContainerGuid { get; set; } = Guid.NewGuid(); - + public abstract Mock GetMock(); } -public abstract class ObjectServiceBase(string key) : ServiceBase (key) +public abstract class ObjectServiceBase(string key) : ServiceBase(key) { - public abstract Mock GetMock(); - + public abstract Mock GetMock(); + public Guid ContainerGuid { get; set; } = Guid.NewGuid(); } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs index c0b69a3..2734a26 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerStub.cs @@ -1,4 +1,5 @@ using FrostFS.Container; + using Moq; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index c76cc9f..d77322a 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -1,14 +1,15 @@ using FrostFS.Container; -using Google.Protobuf; -using Grpc.Core; -using Moq; - -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ClientV2; - -using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.Session; using FrostFS.Refs; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.Session; + +using Google.Protobuf; + +using Grpc.Core; + +using Moq; namespace FrostFS.SDK.Tests; @@ -67,8 +68,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) { Version = grpcVersion, Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), - BasicAcl = (uint)Acl, - PlacementPolicy = PlacementPolicy.ToMessage() + PlacementPolicy = PlacementPolicy.GetPolicy() } }, MetaHeader = ResponseMetaHeader @@ -78,10 +78,10 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) var getNoContainerResponse = new GetResponse { - Body = new (), + Body = new(), MetaHeader = new ResponseMetaHeader - { - Status = new Status.Status + { + Status = new Status.Status { Code = 3072, Message = "container not found" @@ -107,7 +107,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.NotFound, string.Empty), () => ResponseMetaData, - () => { }); + () => { }); } return new AsyncUnaryCall( @@ -115,7 +115,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) Task.FromResult(ResponseMetaData), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), () => ResponseMetaData, - () => { }); + () => { }); }); var listResponse = new ListResponse diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs index 8af698b..f60a2c8 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/RequestData.cs @@ -9,4 +9,3 @@ public class RequestData(T request, Metadata m, DateTime? dt, CancellationTok public DateTime? Deadline => dt; public CancellationToken CancellationToken => ct; } - \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs index 8a63410..72b3aab 100644 --- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -1,8 +1,11 @@ -using Moq; using FrostFS.Netmap; -using Grpc.Core; + using Google.Protobuf; +using Grpc.Core; + +using Moq; + namespace FrostFS.SDK.Tests; public class NetworkMocker(string key) : ServiceBase(key) diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 7e26a3e..1dc9152 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -1,12 +1,16 @@ -using Google.Protobuf; -using Grpc.Core; -using Moq; -using FrostFS.SDK.ClientV2; -using FrostFS.Object; -using FrostFS.SDK.ClientV2.Mappers.GRPC; using System.Security.Cryptography; + +using FrostFS.Object; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; +using Google.Protobuf; + +using Grpc.Core; + +using Moq; + namespace FrostFS.SDK.Tests; public class ObjectMocker(string key) : ObjectServiceBase(key) @@ -183,7 +187,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) () => { }); }); } - + return mock; } @@ -195,7 +199,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) public List? ResultObjectIds { get; set; } - public ClientStreamWriter? ClientStreamWriter { get; private set; } = new (); + public ClientStreamWriter? ClientStreamWriter { get; private set; } = new(); public List PutSingleRequests { get; private set; } = []; diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index e1800b6..359f10b 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -1,6 +1,9 @@ using FrostFS.Session; + using Google.Protobuf; + using Grpc.Core; + using Moq; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 11a7ff0..0079d82 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; + using FrostFS.Netmap; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; @@ -8,8 +10,6 @@ using Google.Protobuf; using Microsoft.Extensions.Options; -using System.Security.Cryptography; - namespace FrostFS.SDK.Tests; public abstract class NetworkTestsBase @@ -40,7 +40,7 @@ public abstract class NetworkTestsBase protected IFrostFSClient GetClient() { - return ClientV2.Client.GetTestInstance( + return ClientV2.FrostFSClient.GetTestInstance( Settings, null, Mocker.GetMock().Object, @@ -88,9 +88,9 @@ public class NetworkTest : NetworkTestsBase } var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - + var result = await GetClient().GetNetworkSettingsAsync(param); - + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); Assert.NotNull(result); @@ -105,7 +105,7 @@ public class NetworkTest : NetworkTestsBase Assert.Equal(Mocker.Parameters["MaxECParityCount"], [(byte)result.MaxECParityCount]); Assert.Equal(Mocker.Parameters["MaxObjectSize"], [(byte)result.MaxObjectSize]); Assert.Equal(Mocker.Parameters["WithdrawFee"], [(byte)result.WithdrawFee]); - + Assert.True(result.HomomorphicHashingDisabled); Assert.True(result.MaintenanceModeAllowed); @@ -142,13 +142,13 @@ public class NetworkTest : NetworkTestsBase nodeInfo1.Addresses.Add("address1"); nodeInfo1.Addresses.Add("address2"); - nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"}); + nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); var nodeInfo2 = new NodeInfo { State = NodeInfo.Types.State.Offline, - PublicKey = ByteString.CopyFrom([3,4,5]) + PublicKey = ByteString.CopyFrom([3, 4, 5]) }; nodeInfo2.Addresses.Add("address3"); @@ -190,7 +190,7 @@ public class NetworkTest : NetworkTestsBase Assert.Equal(2, node1.Addresses.Count); Assert.Equal("address1", node1.Addresses.ElementAt(0)); Assert.Equal("address2", node1.Addresses.ElementAt(1)); - + Assert.Equal(2, node1.Attributes.Count); Assert.Equal("key1", node1.Attributes.ElementAt(0).Key); @@ -202,7 +202,7 @@ public class NetworkTest : NetworkTestsBase Assert.Equal(NodeState.Offline, node2.State); Assert.Single(node2.Addresses); Assert.Equal("address3", node2.Addresses.ElementAt(0)); - + Assert.Single(node2.Attributes); Assert.Equal("key3", node2.Attributes.ElementAt(0).Key); @@ -241,10 +241,10 @@ public class NetworkTest : NetworkTestsBase }, Version = new Refs.Version { Major = 2, Minor = 12 } }; - + body.NodeInfo.Addresses.Add("address1"); body.NodeInfo.Addresses.Add("address2"); - body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1"}); + body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; @@ -273,11 +273,11 @@ public class NetworkTest : NetworkTestsBase Assert.NotNull(result); Assert.Equal(NodeState.Online, result.State); - + Assert.Equal(2, result.Addresses.Count); Assert.Equal("address1", result.Addresses.ElementAt(0)); Assert.Equal("address2", result.Addresses.ElementAt(1)); - + Assert.Equal(2, result.Attributes.Count); Assert.Equal("value1", result.Attributes["key1"]); Assert.Equal("value2", result.Attributes["key2"]); diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 37fe81a..af58346 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -1,3 +1,6 @@ +using System.Security.Cryptography; +using System.Text; + using FrostFS.Refs; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; @@ -9,9 +12,6 @@ using Google.Protobuf; using Microsoft.Extensions.Options; -using System.Security.Cryptography; -using System.Text; - namespace FrostFS.SDK.Tests; public abstract class ObjectTestsBase @@ -29,7 +29,7 @@ public abstract class ObjectTestsBase protected ObjectTestsBase() { var ecdsaKey = key.LoadWif(); - + Settings = Options.Create(new SingleOwnerClientSettings { Key = key, @@ -46,17 +46,17 @@ public abstract class ObjectTestsBase ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); Mocker.ObjectHeader = new( - ContainerId, - FrostFsObjectType.Regular, - [new FrostFsAttribute("k", "v")], + ContainerId, + FrostFsObjectType.Regular, + [new FrostFsAttributePair("k", "v")], null, FrostFsOwner.FromKey(ecdsaKey), new FrostFsVersion(2, 13)); } protected IFrostFSClient GetClient() - { - return Client.GetTestInstance( + { + return FrostFSClient.GetTestInstance( Settings, null, NetworkMocker.GetMock().Object, @@ -75,30 +75,32 @@ public class ObjectTest : ObjectTestsBase var ecdsaKey = key.LoadWif(); - var ctx = new Context { - Key = ecdsaKey, - OwnerId = FrostFsOwner.FromKey(ecdsaKey), - Version = new FrostFsVersion(2, 13) }; + var ctx = new Context + { + Key = ecdsaKey, + OwnerId = FrostFsOwner.FromKey(ecdsaKey), + Version = new FrostFsVersion(2, 13) + }; var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx); - + var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx }); Assert.NotNull(result); - Assert.Equal(Mocker.ObjectHeader!.ContainerId.Value, result.Header.ContainerId.Value); + Assert.Equal(Mocker.ObjectHeader!.ContainerId.GetValue(), result.Header.ContainerId.GetValue()); Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); Assert.Single(result.Header.Attributes); Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); - Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value,result.Header.Attributes[0].Value); + Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value, result.Header.Attributes[0].Value); } [Fact] public async void PutObjectTest() { Mocker.ResultObjectIds = new([SHA256.HashData([])]); - + Random rnd = new(); var bytes = new byte[1024]; rnd.NextBytes(bytes); @@ -119,20 +121,20 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(result); Assert.Equal(Mocker.ResultObjectIds.First(), result.ToHash()); - + Assert.True(Mocker.ClientStreamWriter.CompletedTask); Assert.Equal(0, body1!.Chunk.Length); Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Init, body1!.ObjectPartCase); Assert.Equal(1024, body2!.Chunk.Length); - Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Chunk, body2!.ObjectPartCase); + Assert.Equal(Object.PutRequest.Types.Body.ObjectPartOneofCase.Chunk, body2!.ObjectPartCase); } [Fact] public async void ClientCutTest() { - NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } }; + NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } }; var blockSize = 2560; byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); @@ -251,11 +253,11 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(Mocker.ObjectId.ToMessage(), request.Body.Address.ObjectId); Assert.NotNull(response); - Assert.Equal(ContainerId.Value, response.ContainerId.Value); + Assert.Equal(ContainerId.GetValue(), response.ContainerId.GetValue()); Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, response.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); - + Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); Assert.Equal(FrostFsObjectType.Regular, response.ObjectType); diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index 7eda05e..2e17816 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -1,3 +1,5 @@ +using System.Security.Cryptography; + using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; @@ -6,8 +8,6 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -using System.Security.Cryptography; - namespace FrostFS.SDK.Tests; public abstract class SessionTestsBase @@ -16,7 +16,7 @@ public abstract class SessionTestsBase protected IOptions Settings { get; set; } - + protected ECDsa ECDsaKey { get; set; } protected FrostFsOwner OwnerId { get; set; } protected SessionMocker Mocker { get; set; } @@ -41,7 +41,7 @@ public abstract class SessionTestsBase protected IFrostFSClient GetClient() { - return ClientV2.Client.GetTestInstance( + return ClientV2.FrostFSClient.GetTestInstance( Settings, null, new NetworkMocker(this.key).GetMock().Object, @@ -100,8 +100,8 @@ public class SessionTest : SessionTestsBase Assert.Equal(exp, Mocker.CreateSessionRequest.Body.Expiration); Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader); Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version); - - + + Assert.Null(Mocker.Metadata); if (useContext) diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeTests.cs index 548d757..9c86ff9 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeTests.cs @@ -11,16 +11,16 @@ namespace FrostFS.SDK.SmokeTests; public class SmokeTests : SmokeTestsBase { - private static readonly PrmWait lightWait = new (100, 1); + private static readonly PrmWait lightWait = new(100, 1); [Theory] [InlineData(false)] [InlineData(true)] public async void NetworkMapTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); - - PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new () { Context = Ctx }; + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + + PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; var result = await client.GetNetmapSnapshotAsync(prm); Assert.True(result.Epoch > 0); @@ -41,7 +41,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(true)] public async void NodeInfoTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; @@ -63,9 +63,9 @@ public class SmokeTests : SmokeTestsBase Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") }; - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); - var result = await client.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(); } [Theory] @@ -73,7 +73,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(true)] public async void GetSessionTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : Client.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx }; @@ -82,7 +82,7 @@ public class SmokeTests : SmokeTestsBase var session = new Session.SessionToken().Deserialize(token.Token); var ownerHash = Base58.Decode(OwnerId.Value); - + Assert.NotNull(session); Assert.Null(session.Body.Container); Assert.Null(session.Body.Object); @@ -96,14 +96,14 @@ public class SmokeTests : SmokeTestsBase [Fact] public async void CreateObjectWithSessionToken() { - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); - + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + await Cleanup(client); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); createContainerParam.XHeaders.Add("key1", "value1"); @@ -116,39 +116,44 @@ public class SmokeTests : SmokeTestsBase Header = new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, - [new FrostFsAttribute("fileName", "test")]), + [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, - SessionToken = token + SessionToken = token }; - - var objectId = await client.PutObjectAsync(param); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + var objectId = await client.PutObjectAsync(param).ConfigureAwait(true); + + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)) + .ConfigureAwait(true); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) { ms.Write(chunk.Value.Span); } - Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - await Cleanup(client); + await Cleanup(client).ConfigureAwait(true); } [Fact] public async void FilterTest() { - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + + //var prm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Namespace, "root")); + + //var chains = await client.ListChainAsync(prm); await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { WaitParams = lightWait }; @@ -169,7 +174,7 @@ public class SmokeTests : SmokeTestsBase Header = new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, - [new FrostFsAttribute("fileName", "test")], + [new FrostFsAttributePair("fileName", "test")], new FrostFsSplit()), Payload = new MemoryStream(bytes), ClientCut = false @@ -189,7 +194,7 @@ public class SmokeTests : SmokeTestsBase await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split.SplitId)); - await CheckFilter(client, containerId, new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test")); + await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); @@ -227,14 +232,14 @@ public class SmokeTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); - + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + await Cleanup(client); bool callbackInvoked = false; var ctx = new Context { - // Timeout = TimeSpan.FromSeconds(20), + // Timeout = TimeSpan.FromSeconds(20), Callback = new((CallStatistics cs) => { callbackInvoked = true; @@ -243,7 +248,7 @@ public class SmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)),[new ("testKey", "testValue")])) + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])) { Context = ctx }; @@ -261,7 +266,7 @@ public class SmokeTests : SmokeTestsBase Header = new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttribute("fileName", "test")]), + [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, Context = new Context @@ -272,7 +277,7 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) @@ -315,7 +320,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); @@ -328,7 +333,7 @@ public class SmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { Context = ctx }; @@ -345,7 +350,7 @@ public class SmokeTests : SmokeTestsBase Header = new FrostFsObjectHeader( containerId: container, type: FrostFsObjectType.Regular, - [new FrostFsAttribute("fileName", "test")]), + [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, Context = new Context @@ -357,7 +362,7 @@ public class SmokeTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) @@ -402,12 +407,12 @@ public class SmokeTests : SmokeTestsBase [InlineData(2 * 64 * 1024 * 1024 + 256)] [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) - { - using var client = Client.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + { + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); await Cleanup(client); - var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) { WaitParams = lightWait }; @@ -431,14 +436,14 @@ public class SmokeTests : SmokeTestsBase Header = new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, - [new FrostFsAttribute("fileName", "test")]), + [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = true }; var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttribute(FrostFsMatchType.Equals, "fileName", "test"); + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) @@ -476,16 +481,16 @@ public class SmokeTests : SmokeTestsBase IAsyncEnumerator? 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()); + while (await enumerator!.MoveNextAsync()); } private static byte[] GetRandomBytes(int size) diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index 45f3460..5bf7733 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -8,6 +8,7 @@ namespace FrostFS.SDK.SmokeTests; public abstract class SmokeTestsBase { protected readonly string key = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; + protected readonly string url = "http://172.23.32.4:8080"; protected ECDsa Key { get; } From c9a75ea02558b1c53ca0050b35e47f124cbbfbc7 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 21 Oct 2024 10:48:00 +0300 Subject: [PATCH 24/65] [#24] Client: Implement pool part1 first iteration - base classes and methods Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Cache.cs | 23 - src/FrostFS.SDK.ClientV2/Caches.cs | 22 + src/FrostFS.SDK.ClientV2/CllientKey.cs | 13 +- .../Exceptions/SessionExpiredException.cs | 18 + .../Exceptions/SessionNotFoundException.cs | 18 + src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 91 ++- .../GlobalSuppressions.cs | 8 + .../Interfaces/IFrostFSClient.cs | 16 +- .../Mappers/ContainerId.cs | 4 +- src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs | 8 +- .../Models/Chain/ChainTarget.cs | 93 ++- .../Models/Chain/FrostFsChain.cs | 61 +- .../Models/Chain/FrostFsTargetType.cs | 17 +- .../Models/Session/FrostFsSessionToken.cs | 5 +- .../Parameters/{Context.cs => CallContext.cs} | 2 +- .../Parameters/IContext.cs | 2 +- .../Parameters/PrmBalance.cs | 5 + .../Parameters/PrmBase.cs | 2 +- .../Poll/ClientStatusMonitor.cs | 165 +++++ .../Poll/ClientWrapper.cs | 137 ++++ src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs | 16 + .../Poll/HealthyStatus.cs | 18 + .../Poll/IClientStatus.cs | 28 + .../Poll/InitParameters.cs | 34 + src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs | 47 ++ src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs | 24 + src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs | 19 + src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs | 12 + .../Poll/NodeStatistic.cs | 12 + src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs | 12 + src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 651 ++++++++++++++++++ .../Poll/RebalanceParameters.cs | 16 + src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs | 14 + src/FrostFS.SDK.ClientV2/Poll/Sampler.cs | 85 +++ src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs | 29 + src/FrostFS.SDK.ClientV2/Poll/Statistic.cs | 12 + .../Poll/StatusSnapshot.cs | 8 + src/FrostFS.SDK.ClientV2/Poll/WorkList.cs | 26 + src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs | 34 + .../Services/AccountingServiceProvider.cs | 40 ++ .../{Shared => }/ApeManagerServiceProvider.cs | 10 +- .../Services/ContainerServiceProvider.cs | 14 +- .../Services/NetmapServiceProvider.cs | 30 +- .../Services/ObjectServiceProvider.cs | 30 +- .../Services/SessionServiceProvider.cs | 6 +- .../Services/Shared/ContextAccessor.cs | 4 +- .../Services/Shared/SessionProvider.cs | 9 +- ...ntEnvironment.cs => EnvironmentContext.cs} | 2 +- .../Tools/NetworkSettings.cs | 4 + src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 8 +- .../Tools/RequestSigner.cs | 5 + .../accounting/Extension.Message.cs | 62 ++ src/FrostFS.SDK.Tests/ContainerTest.cs | 41 +- src/FrostFS.SDK.Tests/ContainerTestsBase.cs | 40 ++ src/FrostFS.SDK.Tests/GlobalSuppressions.cs | 6 + src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 6 +- .../Mocks/AsyncStreamReaderMock.cs | 7 +- .../Mocks/ClientStreamWriter.cs | 10 +- .../ContainerServiceBase.cs | 7 +- .../ContainerServiceMocks/GetContainerMock.cs | 8 +- src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs | 13 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 11 +- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 5 +- src/FrostFS.SDK.Tests/NetworkTest.cs | 84 +-- src/FrostFS.SDK.Tests/NetworkTestsBase.cs | 48 ++ src/FrostFS.SDK.Tests/ObjectTest.cs | 84 +-- src/FrostFS.SDK.Tests/ObjectTestsBase.cs | 59 ++ src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 603 ++++++++++++++++ src/FrostFS.SDK.Tests/SessionTests.cs | 52 +- src/FrostFS.SDK.Tests/SessionTestsBase.cs | 48 ++ .../{SmokeTests.cs => SmokeClientTests.cs} | 73 +- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 18 +- 72 files changed, 2786 insertions(+), 468 deletions(-) delete mode 100644 src/FrostFS.SDK.ClientV2/Cache.cs create mode 100644 src/FrostFS.SDK.ClientV2/Caches.cs create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs create mode 100644 src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs rename src/FrostFS.SDK.ClientV2/Parameters/{Context.cs => CallContext.cs} (97%) create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Pool.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Sampler.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/Statistic.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/WorkList.cs create mode 100644 src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs create mode 100644 src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs rename src/FrostFS.SDK.ClientV2/Services/{Shared => }/ApeManagerServiceProvider.cs (90%) rename src/FrostFS.SDK.ClientV2/Tools/{ClientEnvironment.cs => EnvironmentContext.cs} (87%) create mode 100644 src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs create mode 100644 src/FrostFS.SDK.Tests/ContainerTestsBase.cs create mode 100644 src/FrostFS.SDK.Tests/GlobalSuppressions.cs create mode 100644 src/FrostFS.SDK.Tests/NetworkTestsBase.cs create mode 100644 src/FrostFS.SDK.Tests/ObjectTestsBase.cs create mode 100644 src/FrostFS.SDK.Tests/PoolSmokeTests.cs create mode 100644 src/FrostFS.SDK.Tests/SessionTestsBase.cs rename src/FrostFS.SDK.Tests/{SmokeTests.cs => SmokeClientTests.cs} (88%) diff --git a/src/FrostFS.SDK.ClientV2/Cache.cs b/src/FrostFS.SDK.ClientV2/Cache.cs deleted file mode 100644 index 19ec83b..0000000 --- a/src/FrostFS.SDK.ClientV2/Cache.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.Extensions.Caching.Memory; - -namespace FrostFS.SDK.ClientV2 -{ - internal static class Cache - { - private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions - { - // TODO: get from options? - SizeLimit = 256 - }); - - private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions - { - // TODO: get from options? - SizeLimit = 1024 - }); - - internal static IMemoryCache Owners => _ownersCache; - - internal static IMemoryCache Containers => _containersCache; - } -} diff --git a/src/FrostFS.SDK.ClientV2/Caches.cs b/src/FrostFS.SDK.ClientV2/Caches.cs new file mode 100644 index 0000000..ae6a290 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Caches.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Caching.Memory; + +namespace FrostFS.SDK.ClientV2; + +internal static class Caches +{ + private static readonly IMemoryCache _ownersCache = new MemoryCache(new MemoryCacheOptions + { + // TODO: get from options? + SizeLimit = 256 + }); + + private static readonly IMemoryCache _containersCache = new MemoryCache(new MemoryCacheOptions + { + // TODO: get from options? + SizeLimit = 1024 + }); + + internal static IMemoryCache Owners => _ownersCache; + + internal static IMemoryCache Containers => _containersCache; +} diff --git a/src/FrostFS.SDK.ClientV2/CllientKey.cs b/src/FrostFS.SDK.ClientV2/CllientKey.cs index 5b10485..a7542cb 100644 --- a/src/FrostFS.SDK.ClientV2/CllientKey.cs +++ b/src/FrostFS.SDK.ClientV2/CllientKey.cs @@ -4,12 +4,11 @@ using FrostFS.SDK.Cryptography; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2 -{ - public class ClientKey(ECDsa key) - { - internal ECDsa ECDsaKey { get; } = key; +namespace FrostFS.SDK.ClientV2; - internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey()); - } +public class ClientKey(ECDsa key) +{ + internal ECDsa ECDsaKey { get; } = key; + + internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey()); } diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs new file mode 100644 index 0000000..44404dd --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class SessionExpiredException : FrostFsException +{ + public SessionExpiredException() + { + } + + public SessionExpiredException(string message) : base(message) + { + } + + public SessionExpiredException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs new file mode 100644 index 0000000..5ec0a09 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class SessionNotFoundException : FrostFsException +{ + public SessionNotFoundException() + { + } + + public SessionNotFoundException(string message) : base(message) + { + } + + public SessionNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs index 4a680de..2ca9e96 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs @@ -6,10 +6,12 @@ using System.Threading.Tasks; using Frostfs.V2.Ape; using Frostfs.V2.Apemanager; +using FrostFS.Accounting; using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Services; using FrostFS.SDK.Cryptography; using FrostFS.Session; @@ -35,7 +37,9 @@ public class FrostFSClient : IFrostFSClient internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } - internal ClientEnvironment ClientCtx { get; set; } + internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; } + + internal EnvironmentContext ClientCtx { get; set; } public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { @@ -89,7 +93,7 @@ public class FrostFSClient : IFrostFSClient var ecdsaKey = settings.Value.Key.LoadWif(); FrostFsOwner.FromKey(ecdsaKey); - ClientCtx = new ClientEnvironment( + ClientCtx = new EnvironmentContext( client: this, key: ecdsaKey, owner: FrostFsOwner.FromKey(ecdsaKey), @@ -110,7 +114,7 @@ public class FrostFSClient : IFrostFSClient var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - ClientCtx = new ClientEnvironment( + ClientCtx = new EnvironmentContext( this, key: null, owner: null, @@ -131,7 +135,7 @@ public class FrostFSClient : IFrostFSClient var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - ClientCtx = new ClientEnvironment( + ClientCtx = new EnvironmentContext( this, key: ecdsaKey, owner: FrostFsOwner.FromKey(ecdsaKey), @@ -142,6 +146,16 @@ public class FrostFSClient : IFrostFSClient // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); } + internal FrostFSClient(WrapperPrm prm) + { + ClientCtx = new EnvironmentContext( + client: this, + key: prm.Key, + owner: FrostFsOwner.FromKey(prm.Key!), + channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions), + version: new FrostFsVersion(2, 13)); + } + public void Dispose() { Dispose(true); @@ -305,16 +319,16 @@ public class FrostFSClient : IFrostFSClient } #endregion - #region SessionImplementation + #region Session Implementation public async Task CreateSessionAsync(PrmSessionCreate args) { if (args is null) throw new ArgumentNullException(nameof(args)); var session = await CreateSessionInternalAsync(args).ConfigureAwait(false); - var token = session.Serialize(); - return new FrostFsSessionToken(token); + var token = session.Serialize(); + return new FrostFsSessionToken(token, session.Body.Id.ToUuid()); } internal Task CreateSessionInternalAsync(PrmSessionCreate args) @@ -327,8 +341,18 @@ public class FrostFSClient : IFrostFSClient } #endregion + #region Accounting Implementation + public async Task GetBalanceAsync(PrmBalance? args) + { + args ??= new PrmBalance(); + + var service = GetAccouningService(args); + return await service.GetBallance(args).ConfigureAwait(false); + } + #endregion + #region ToolsImplementation - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx) + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) { if (header == null) throw new ArgumentNullException(nameof(header)); @@ -337,7 +361,7 @@ public class FrostFSClient : IFrostFSClient } #endregion - private async void CheckFrostFsVersionSupport(Context? ctx = default) + private async void CheckFrostFsVersionSupport(CallContext? ctx = default) { var args = new PrmNodeInfo { Context = ctx }; @@ -359,7 +383,7 @@ public class FrostFSClient : IFrostFSClient if (isDisposed) throw new InvalidObjectException("Client is disposed."); - ctx.Context ??= new Context(); + ctx.Context ??= new CallContext(); if (ctx.Context.Key == null) { @@ -441,6 +465,16 @@ public class FrostFSClient : IFrostFSClient return new ApeManagerServiceProvider(client, ClientCtx); } + private AccountingServiceProvider GetAccouningService(IContext ctx) + { + var callInvoker = SetupEnvironment(ctx); + var client = AccountingServiceClient ?? (callInvoker != null + ? new AccountingService.AccountingServiceClient(callInvoker) + : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); + + return new AccountingServiceProvider(client, ClientCtx); + } + private ContainerServiceProvider GetContainerService(IContext ctx) { var callInvoker = SetupEnvironment(ctx); @@ -461,6 +495,16 @@ public class FrostFSClient : IFrostFSClient return new ObjectServiceProvider(client, ClientCtx); } + private AccountingServiceProvider GetAccountService(IContext ctx) + { + var callInvoker = SetupEnvironment(ctx); + var client = AccountingServiceClient ?? (callInvoker != null + ? new AccountingService.AccountingServiceClient(callInvoker) + : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); + + return new AccountingServiceProvider(client, ClientCtx); + } + private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) { try @@ -480,4 +524,31 @@ public class FrostFSClient : IFrostFSClient throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}"); } } + + public async Task Dial(CallContext ctx) + { + try + { + var prm = new PrmBalance { Context = ctx }; + + var service = GetAccouningService(prm); + var balance = await service.GetBallance(prm).ConfigureAwait(false); + + return null; + } + catch (FrostFsException ex) + { + return ex.Message; + } + } + + public bool RestartIfUnhealthy(CallContext ctx) + { + throw new NotImplementedException(); + } + + public void Close() + { + Dispose(); + } } diff --git a/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs b/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs new file mode 100644 index 0000000..c87cda6 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "", Scope = "member", Target = "~M:FrostFS.SDK.ClientV2.Sampler.Next~System.Int32")] diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 3af0c0b..193d128 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -19,7 +19,7 @@ public interface IFrostFSClient : IDisposable Task CreateSessionAsync(PrmSessionCreate args); #endregion - #region ApeMAnager + #region ApeManager Task AddChainAsync(PrmApeChainAdd args); Task RemoveChainAsync(PrmApeChainRemove args); @@ -51,7 +51,17 @@ public interface IFrostFSClient : IDisposable IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); #endregion - #region Tools - FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx); + #region Account + Task GetBalanceAsync(PrmBalance? args = null); #endregion + + #region Tools + FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx); + #endregion + + public Task Dial(CallContext ctx); + + public bool RestartIfUnhealthy(CallContext ctx); + + public void Close(); } diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index 86449e2..7a5359d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -24,14 +24,14 @@ public static class ContainerIdMapper var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model)); - if (!Cache.Containers.TryGetValue(containerId, out ContainerID? message)) + if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message)) { message = new ContainerID { Value = ByteString.CopyFrom(Base58.Decode(containerId)) }; - Cache.Containers.Set(containerId, message, _oneHourExpiration); + Caches.Containers.Set(containerId, message, _oneHourExpiration); } return message!; diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs index 6f3276d..c2e61e6 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs @@ -22,14 +22,14 @@ public static class OwnerIdMapper throw new ArgumentNullException(nameof(model)); } - if (!Cache.Owners.TryGetValue(model, out OwnerID? message)) + if (!Caches.Owners.TryGetValue(model, out OwnerID? message)) { message = new OwnerID { Value = ByteString.CopyFrom(model.ToHash()) }; - Cache.Owners.Set(model, message, _oneHourExpiration); + Caches.Owners.Set(model, message, _oneHourExpiration); } return message!; @@ -42,11 +42,11 @@ public static class OwnerIdMapper throw new ArgumentNullException(nameof(message)); } - if (!Cache.Owners.TryGetValue(message, out FrostFsOwner? model)) + if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model)) { model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray())); - Cache.Owners.Set(message, model, _oneHourExpiration); + Caches.Owners.Set(message, model, _oneHourExpiration); } return model!; diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs index e9ce527..a009df2 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs @@ -2,62 +2,61 @@ using Frostfs.V2.Ape; -namespace FrostFS.SDK.ClientV2 +namespace FrostFS.SDK.ClientV2; + +public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable { - public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable + private ChainTarget? chainTarget; + + public FrostFsTargetType Type { get; } = type; + + public string Name { get; } = name; + + internal ChainTarget GetChainTarget() { - private ChainTarget? chainTarget; - - public FrostFsTargetType Type { get; } = type; - - public string Name { get; } = name; - - internal ChainTarget GetChainTarget() + return chainTarget ??= new ChainTarget { - return chainTarget ??= new ChainTarget - { - Type = GetTargetType(Type), - Name = Name - }; - } + Type = GetTargetType(Type), + Name = Name + }; + } - private static TargetType GetTargetType(FrostFsTargetType type) + private static TargetType GetTargetType(FrostFsTargetType type) + { + return type switch { - return type switch - { - FrostFsTargetType.Undefined => TargetType.Undefined, - FrostFsTargetType.Namespace => TargetType.Namespace, - FrostFsTargetType.Container => TargetType.Container, - FrostFsTargetType.User => TargetType.User, - FrostFsTargetType.Group => TargetType.Group, - _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)), - }; - } + FrostFsTargetType.Undefined => TargetType.Undefined, + FrostFsTargetType.Namespace => TargetType.Namespace, + FrostFsTargetType.Container => TargetType.Container, + FrostFsTargetType.User => TargetType.User, + FrostFsTargetType.Group => TargetType.Group, + _ => throw new ArgumentException("Unexpected value for TargetType", nameof(type)), + }; + } - public override readonly bool Equals(object obj) - { - var target = (FrostFsChainTarget)obj; - return Equals(target); - } + public override readonly bool Equals(object obj) + { + var target = (FrostFsChainTarget)obj; + return Equals(target); + } - public override readonly int GetHashCode() - { - return $"{Name}{Type}".GetHashCode(); - } + public override readonly int GetHashCode() + { + return $"{Name}{Type}".GetHashCode(); + } - public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right) - { - return left.Equals(right); - } + public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right) + { + return left.Equals(right); + } - public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right) - { - return !(left == right); - } + public static bool operator !=(FrostFsChainTarget left, FrostFsChainTarget right) + { + return !(left == right); + } - public readonly bool Equals(FrostFsChainTarget other) - { - return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal); - } + public readonly bool Equals(FrostFsChainTarget other) + { + return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal); } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs index d6b4b1d..1f4d251 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs @@ -1,42 +1,41 @@ using Google.Protobuf; -namespace FrostFS.SDK.ClientV2 +namespace FrostFS.SDK.ClientV2; + +public struct FrostFsChain(byte[] raw) : System.IEquatable { - public struct FrostFsChain(byte[] raw) : System.IEquatable + private ByteString? grpcRaw; + + public byte[] Raw { get; } = raw; + + internal ByteString GetRaw() { - private ByteString? grpcRaw; + return grpcRaw ??= ByteString.CopyFrom(Raw); + } - public byte[] Raw { get; } = raw; + public override readonly bool Equals(object obj) + { + var chain = (FrostFsChain)obj; + return Equals(chain); + } - internal ByteString GetRaw() - { - return grpcRaw ??= ByteString.CopyFrom(Raw); - } + public override readonly int GetHashCode() + { + return Raw.GetHashCode(); + } - public override readonly bool Equals(object obj) - { - var chain = (FrostFsChain)obj; - return Equals(chain); - } + public static bool operator ==(FrostFsChain left, FrostFsChain right) + { + return left.Equals(right); + } - public override readonly int GetHashCode() - { - return Raw.GetHashCode(); - } + public static bool operator !=(FrostFsChain left, FrostFsChain right) + { + return !(left == right); + } - 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; - } + public readonly bool Equals(FrostFsChain other) + { + return Raw == other.Raw; } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs index 03b1521..ed5fa27 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs @@ -1,11 +1,10 @@ -namespace FrostFS.SDK.ClientV2 +namespace FrostFS.SDK.ClientV2; + +public enum FrostFsTargetType { - public enum FrostFsTargetType - { - Undefined = 0, - Namespace, - Container, - User, - Group - } + Undefined = 0, + Namespace, + Container, + User, + Group } diff --git a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs index 8954780..672754d 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs @@ -1,6 +1,9 @@ +using System; + namespace FrostFS.SDK; -public class FrostFsSessionToken(byte[] token) +public class FrostFsSessionToken(byte[] token, Guid id) { + public Guid Id { get; private set; } = id; public byte[] Token { get; private set; } = token; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Parameters/Context.cs rename to src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs index 0e96929..ff4bc27 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/Context.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs @@ -11,7 +11,7 @@ using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; -public class Context() +public class CallContext() { private ReadOnlyCollection? interceptors; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs index bd333f7..8c6f1df 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs @@ -7,5 +7,5 @@ public interface IContext /// callbacks, interceptors. /// /// Additional parameters for calling the method - Context? Context { get; set; } + CallContext? Context { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs new file mode 100644 index 0000000..3a07fde --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs @@ -0,0 +1,5 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmBalance() : PrmBase +{ +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs index 2a6e542..79e5e7e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs @@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext public NameValueCollection XHeaders { get; } = xheaders ?? []; /// - public Context? Context { get; set; } + public CallContext? Context { get; set; } } diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs new file mode 100644 index 0000000..e864148 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs @@ -0,0 +1,165 @@ +using System; +using System.Threading; + +using Microsoft.Extensions.Logging; + +namespace FrostFS.SDK.ClientV2; + +// clientStatusMonitor count error rate and other statistics for connection. +public class ClientStatusMonitor : IClientStatus +{ + private static readonly MethodIndex[] MethodIndexes = + [ + MethodIndex.methodBalanceGet, + MethodIndex.methodContainerPut, + MethodIndex.methodContainerGet, + MethodIndex.methodContainerList, + MethodIndex.methodContainerDelete, + MethodIndex.methodEndpointInfo, + MethodIndex.methodNetworkInfo, + MethodIndex.methodNetMapSnapshot, + MethodIndex.methodObjectPut, + MethodIndex.methodObjectDelete, + MethodIndex.methodObjectGet, + MethodIndex.methodObjectHead, + MethodIndex.methodObjectRange, + MethodIndex.methodObjectPatch, + MethodIndex.methodSessionCreate, + MethodIndex.methodAPEManagerAddChain, + MethodIndex.methodAPEManagerRemoveChain, + MethodIndex.methodAPEManagerListChains, + MethodIndex.methodLast + ]; + + public static string GetMethodName(MethodIndex index) + { + return index switch + { + MethodIndex.methodBalanceGet => "BalanceGet", + MethodIndex.methodContainerPut => "ContainerPut", + MethodIndex.methodContainerGet => "ContainerGet", + MethodIndex.methodContainerList => "ContainerList", + MethodIndex.methodContainerDelete => "ContainerDelete", + MethodIndex.methodEndpointInfo => "EndpointInfo", + MethodIndex.methodNetworkInfo => "NetworkInfo", + MethodIndex.methodNetMapSnapshot => "NetMapSnapshot", + MethodIndex.methodObjectPut => "ObjectPut", + MethodIndex.methodObjectDelete => "ObjectDelete", + MethodIndex.methodObjectGet => "ObjectGet", + MethodIndex.methodObjectHead => "ObjectHead", + MethodIndex.methodObjectRange => "ObjectRange", + MethodIndex.methodObjectPatch => "ObjectPatch", + MethodIndex.methodSessionCreate => "SessionCreate", + MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain", + MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain", + MethodIndex.methodAPEManagerListChains => "APEManagerListChains", + _ => throw new NotImplementedException(), + }; + } + + private readonly object _lock = new(); + + private readonly ILogger? logger; + private int healthy; + + public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold) + { + this.logger = logger; + healthy = (int)HealthyStatus.Healthy; + Address = address; + ErrorThreshold = errorThreshold; + + Methods = new MethodStatus[MethodIndexes.Length]; + + for (int i = 0; i < MethodIndexes.Length; i++) + { + Methods[i] = new MethodStatus(GetMethodName(MethodIndexes[i])); + } + } + + public string Address { get; } + + internal uint ErrorThreshold { get; } + + public uint CurrentErrorCount { get; set; } + + public ulong OverallErrorCount { get; set; } + + public MethodStatus[] Methods { get; private set; } + + public bool IsHealthy() + { + return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy; + } + + public bool IsDialed() + { + return Interlocked.CompareExchange(ref healthy, -1, -1) != (int)HealthyStatus.UnhealthyOnDial; + } + + public void SetHealthy() + { + Interlocked.Exchange(ref healthy, (int)HealthyStatus.Healthy); + } + public void SetUnhealthy() + { + Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnRequest); + } + + public void SetUnhealthyOnDial() + { + Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnDial); + } + + public void IncErrorRate() + { + bool thresholdReached; + lock (_lock) + { + CurrentErrorCount++; + OverallErrorCount++; + + thresholdReached = CurrentErrorCount >= ErrorThreshold; + + if (thresholdReached) + { + SetUnhealthy(); + + CurrentErrorCount = 0; + } + } + + if (thresholdReached) + { + logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold); + } + } + + public uint GetCurrentErrorRate() + { + lock (_lock) + { + return CurrentErrorCount; + } + } + + public ulong GetOverallErrorRate() + { + lock (_lock) + { + return OverallErrorCount; + } + } + + public StatusSnapshot[] MethodsStatus() + { + var result = new StatusSnapshot[Methods.Length]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = Methods[i].Snapshot!; + } + + return result; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs new file mode 100644 index 0000000..3891250 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs @@ -0,0 +1,137 @@ +using System.Threading.Tasks; + +namespace FrostFS.SDK.ClientV2; + +// clientWrapper is used by default, alternative implementations are intended for testing purposes only. +public class ClientWrapper +{ + private readonly object _lock = new(); + + public ClientWrapper(WrapperPrm wrapperPrm) + { + WrapperPrm = wrapperPrm; + StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold); + + try + { + Client = new FrostFSClient(WrapperPrm); + StatusMonitor.SetHealthy(); + } + catch (FrostFsException) + { + } + } + + internal FrostFSClient? Client { get; private set; } + + internal WrapperPrm WrapperPrm { get; } + + internal ClientStatusMonitor StatusMonitor { get; } + + internal FrostFSClient? GetClient() + { + lock (_lock) + { + if (StatusMonitor.IsHealthy()) + { + return Client; + } + + return null; + } + } + + // dial establishes a connection to the server from the FrostFS network. + // Returns an error describing failure reason. If failed, the client + // SHOULD NOT be used. + internal async Task Dial(CallContext ctx) + { + var client = GetClient(); + + if (client == null) + return "pool client unhealthy"; + + var result = await client.Dial(ctx).ConfigureAwait(false); + if (!string.IsNullOrEmpty(result)) + { + StatusMonitor.SetUnhealthyOnDial(); + return result; + } + + return null; + } + + private async Task ScheduleGracefulClose() + { + if (Client == null) + return; + + await Task.Delay((int)WrapperPrm.GracefulCloseOnSwitchTimeout).ConfigureAwait(false); + + Client.Close(); + } + + // restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy. + // Indicating if status was changed by this function call and returns error that caused unhealthy status. + internal async Task RestartIfUnhealthy(CallContext ctx) + { + bool wasHealthy; + + try + { + var prmNodeInfo = new PrmNodeInfo { Context = ctx }; + var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); + return false; + } + catch (FrostFsException) + { + wasHealthy = true; + } + + // if connection is dialed before, to avoid routine/connection leak, + // pool has to close it and then initialize once again. + if (StatusMonitor.IsDialed()) + { + await ScheduleGracefulClose().ConfigureAwait(false); + } + +#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually + FrostFSClient client = new(WrapperPrm); +#pragma warning restore CA2000 + + //TODO: set additioanl params + var error = await client.Dial(ctx).ConfigureAwait(false); + if (!string.IsNullOrEmpty(error)) + { + StatusMonitor.SetUnhealthyOnDial(); + return wasHealthy; + } + + lock (_lock) + { + Client = client; + } + + try + { + var prmNodeInfo = new PrmNodeInfo { Context = ctx }; + var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); + } + catch (FrostFsException) + { + StatusMonitor.SetUnhealthy(); + return wasHealthy; + } + + StatusMonitor.SetHealthy(); + return !wasHealthy; + } + + internal void IncRequests(ulong elapsed, MethodIndex method) + { + var methodStat = StatusMonitor.Methods[(int)method]; + + methodStat.IncRequests(elapsed); + } +} + diff --git a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs new file mode 100644 index 0000000..813d373 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs @@ -0,0 +1,16 @@ +namespace FrostFS.SDK.ClientV2; + +public class DialOptions +{ + public bool Block { get; set; } + + public bool ReturnLastError { get; set; } + + public ulong Timeout { get; set; } + + public string? Authority { get; set; } + + public bool DisableRetry { get; set; } + + public bool DisableHealthCheck { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs new file mode 100644 index 0000000..f87c1dd --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs @@ -0,0 +1,18 @@ +namespace FrostFS.SDK.ClientV2; + +// values for healthy status of clientStatusMonitor. +public enum HealthyStatus +{ + // statusUnhealthyOnDial is set when dialing to the endpoint is failed, + // so there is no connection to the endpoint, and pool should not close it + // before re-establishing connection once again. + UnhealthyOnDial, + + // statusUnhealthyOnRequest is set when communication after dialing to the + // endpoint is failed due to immediate or accumulated errors, connection is + // available and pool should close it before re-establishing connection once again. + UnhealthyOnRequest, + + // statusHealthy is set when connection is ready to be used by the pool. + Healthy +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs new file mode 100644 index 0000000..cbf597e --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs @@ -0,0 +1,28 @@ +namespace FrostFS.SDK.ClientV2; + +public interface IClientStatus +{ + // isHealthy checks if the connection can handle requests. + bool IsHealthy(); + + // isDialed checks if the connection was created. + bool IsDialed(); + + // setUnhealthy marks client as unhealthy. + void SetUnhealthy(); + + // address return address of endpoint. + string Address { get; } + + // currentErrorRate returns current errors rate. + // After specific threshold connection is considered as unhealthy. + // Pool.startRebalance routine can make this connection healthy again. + uint GetCurrentErrorRate(); + + // overallErrorRate returns the number of all happened errors. + ulong GetOverallErrorRate(); + + // methodsStatus returns statistic for all used methods. + StatusSnapshot[] MethodsStatus(); +} + diff --git a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs new file mode 100644 index 0000000..e05c31a --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs @@ -0,0 +1,34 @@ +using System; +using System.Security.Cryptography; + +using Microsoft.Extensions.Logging; + +namespace FrostFS.SDK.ClientV2; + +// InitParameters contains values used to initialize connection Pool. +public class InitParameters +{ + public ECDsa? Key { get; set; } + + public ulong NodeDialTimeout { get; set; } + + public ulong NodeStreamTimeout { get; set; } + + public ulong HealthcheckTimeout { get; set; } + + public ulong ClientRebalanceInterval { get; set; } + + public ulong SessionExpirationDuration { get; set; } + + public uint ErrorThreshold { get; set; } + + public NodeParam[]? NodeParams { get; set; } + + public DialOptions[]? DialOptions { get; set; } + + public Func? ClientBuilder { get; set; } + + public ulong GracefulCloseOnSwitchTimeout { get; set; } + + public ILogger? Logger { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs new file mode 100644 index 0000000..a535260 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs @@ -0,0 +1,47 @@ +using FrostFS.SDK.ClientV2; + +internal sealed class InnerPool +{ + private readonly object _lock = new(); + + internal InnerPool(Sampler sampler, ClientWrapper[] clients) + { + Clients = clients; + Sampler = sampler; + } + + internal Sampler Sampler { get; set; } + + internal ClientWrapper[] Clients { get; } + + internal ClientWrapper? Connection() + { + lock (_lock) + { + if (Clients.Length == 1) + { + var client = Clients[0]; + if (client.StatusMonitor.IsHealthy()) + { + return client; + } + } + else + { + var attempts = 3 * Clients.Length; + + for (int i = 0; i < attempts; i++) + { + int index = Sampler.Next(); + + if (Clients[index].StatusMonitor.IsHealthy()) + { + return Clients[index]; + } + } + } + + return null; + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs b/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs new file mode 100644 index 0000000..8a7008b --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs @@ -0,0 +1,24 @@ +namespace FrostFS.SDK.ClientV2; + +public enum MethodIndex +{ + methodBalanceGet, + methodContainerPut, + methodContainerGet, + methodContainerList, + methodContainerDelete, + methodEndpointInfo, + methodNetworkInfo, + methodNetMapSnapshot, + methodObjectPut, + methodObjectDelete, + methodObjectGet, + methodObjectHead, + methodObjectRange, + methodObjectPatch, + methodSessionCreate, + methodAPEManagerAddChain, + methodAPEManagerRemoveChain, + methodAPEManagerListChains, + methodLast +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs b/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs new file mode 100644 index 0000000..33cad49 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs @@ -0,0 +1,19 @@ +namespace FrostFS.SDK.ClientV2; + +public class MethodStatus(string name) +{ + private readonly object _lock = new(); + + public string Name { get; } = name; + + public StatusSnapshot Snapshot { get; set; } = new StatusSnapshot(); + + internal void IncRequests(ulong elapsed) + { + lock (_lock) + { + Snapshot.AllTime += elapsed; + Snapshot.AllRequests++; + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs b/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs new file mode 100644 index 0000000..f95772b --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs @@ -0,0 +1,12 @@ +namespace FrostFS.SDK.ClientV2; + +// NodeParam groups parameters of remote node. +[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] +public readonly struct NodeParam(int priority, string address, float weight) +{ + public int Priority { get; } = priority; + + public string Address { get; } = address; + + public float Weight { get; } = weight; +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs b/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs new file mode 100644 index 0000000..d51aa83 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs @@ -0,0 +1,12 @@ +namespace FrostFS.SDK.ClientV2; + +public class NodeStatistic +{ + public string? Address { get; internal set; } + + public StatusSnapshot[]? Methods { get; internal set; } + + public ulong OverallErrors { get; internal set; } + + public uint CurrentErrors { get; internal set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs b/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs new file mode 100644 index 0000000..eaeef7c --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs @@ -0,0 +1,12 @@ +using System.Collections.ObjectModel; + +namespace FrostFS.SDK.ClientV2; + +public class NodesParam(int priority) +{ + public int Priority { get; } = priority; + + public Collection Addresses { get; } = []; + + public Collection Weights { get; } = []; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs new file mode 100644 index 0000000..a304e93 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs @@ -0,0 +1,651 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Frostfs.V2.Ape; + +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Logging; + + +namespace FrostFS.SDK.ClientV2; + +public partial class Pool : IFrostFSClient +{ + const int defaultSessionTokenExpirationDuration = 100; // in epochs + + const int defaultErrorThreshold = 100; + + const int defaultGracefulCloseOnSwitchTimeout = 10; //Seconds; + const int defaultRebalanceInterval = 15; //Seconds; + const int defaultHealthcheckTimeout = 4; //Seconds; + const int defaultDialTimeout = 5; //Seconds; + const int defaultStreamTimeout = 10; //Seconds; + + private readonly object _lock = new(); + + private InnerPool[]? InnerPools { get; set; } + + private ECDsa Key { get; set; } + + private byte[] PublicKey { get; } + + private OwnerID? _ownerId; + private FrostFsOwner? _owner; + + private FrostFsOwner Owner + { + get + { + _owner ??= new FrostFsOwner(Key.PublicKey().PublicKeyToAddress()); + return _owner; + } + } + + private OwnerID OwnerId + { + get + { + if (_ownerId == null) + { + _owner = new FrostFsOwner(Key.PublicKey().PublicKeyToAddress()); + _ownerId = _owner.ToMessage(); + } + return _ownerId; + } + } + + internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + + private SessionCache Cache { get; set; } + + private ulong SessionTokenDuration { get; set; } + + private RebalanceParameters RebalanceParams { get; set; } + + private Func ClientBuilder; + + private bool disposedValue; + + private ILogger? Logger { get; set; } + + private ulong MaxObjectSize { get; set; } + + public IClientStatus? ClientStatus { get; } + + // NewPool creates connection pool using parameters. + public Pool(InitParameters options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.Key == null) + { + throw new FrostFsException($"Missed required parameter {nameof(options.Key)}"); + } + + var nodesParams = AdjustNodeParams(options.NodeParams); + + var cache = new SessionCache(options.SessionExpirationDuration); + + FillDefaultInitParams(options, cache); + + Key = options.Key; + PublicKey = Key.PublicKey(); + + Cache = cache; + Logger = options.Logger; + SessionTokenDuration = options.SessionExpirationDuration; + + RebalanceParams = new RebalanceParameters( + nodesParams.ToArray(), + options.HealthcheckTimeout, + options.ClientRebalanceInterval, + options.SessionExpirationDuration); + + ClientBuilder = options.ClientBuilder!; + } + + private void SetupContext(CallContext ctx) + { + if (ctx == null) + { + throw new ArgumentNullException(nameof(ctx)); + } + + ctx.Key ??= Key; + } + + // Dial establishes a connection to the servers from the FrostFS network. + // It also starts a routine that checks the health of the nodes and + // updates the weights of the nodes for balancing. + // Returns an error describing failure reason. + // + // If failed, the Pool SHOULD NOT be used. + // + // See also InitParameters.SetClientRebalanceInterval. + public async Task Dial(CallContext ctx) + { + SetupContext(ctx); + + var inner = new InnerPool[RebalanceParams.NodesParams.Length]; + + bool atLeastOneHealthy = false; + int i = 0; + foreach (var nodeParams in RebalanceParams.NodesParams) + { + var clients = new ClientWrapper[nodeParams.Weights.Count]; + + for (int j = 0; j < nodeParams.Addresses.Count; j++) + { + var client = ClientBuilder(nodeParams.Addresses[j]); + clients[j] = client; + + var error = await client.Dial(ctx).ConfigureAwait(false); + if (!string.IsNullOrEmpty(error)) + { + Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error); + continue; + } + + try + { + var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false) + .ConfigureAwait(false); + + var key = FormCacheKey(nodeParams.Addresses[j], Key, false); + _ = Cache.Cache[key] = token; + } + catch (FrostFsException ex) + { + client.StatusMonitor.SetUnhealthy(); + Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})", + client.WrapperPrm.Address, ex.Message); + + continue; + } + + atLeastOneHealthy = true; + } + + var sampler = new Sampler(nodeParams.Weights.ToArray()); + + inner[i] = new InnerPool(sampler, clients); + } + + if (!atLeastOneHealthy) + return "at least one node must be healthy"; + + InnerPools = inner; + + var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false); + + MaxObjectSize = res.MaxObjectSize; + + StartRebalance(ctx); + + return null; + } + + private static IEnumerable AdjustNodeParams(NodeParam[]? nodeParams) + { + if (nodeParams == null || nodeParams.Length == 0) + { + throw new ArgumentException("No FrostFS peers configured"); + } + + Dictionary nodesParamsDict = new(nodeParams.Length); + foreach (var nodeParam in nodeParams) + { + if (!nodesParamsDict.TryGetValue(nodeParam.Priority, out var nodes)) + { + nodes = new NodesParam(nodeParam.Priority); + nodesParamsDict[nodeParam.Priority] = nodes; + } + + nodes.Addresses.Add(nodeParam.Address); + nodes.Weights.Add(nodeParam.Weight); + } + + var nodesParams = new List(nodesParamsDict.Count); + + foreach (var key in nodesParamsDict.Keys) + { + var nodes = nodesParamsDict[key]; + var newWeights = AdjustWeights([.. nodes.Weights]); + nodes.Weights.Clear(); + foreach (var weight in newWeights) + { + nodes.Weights.Add(weight); + } + + nodesParams.Add(nodes); + } + + return nodesParams.OrderBy(n => n.Priority); + } + + private static double[] AdjustWeights(double[] weights) + { + var adjusted = new double[weights.Length]; + + var sum = weights.Sum(); + + if (sum > 0) + { + for (int i = 0; i < weights.Length; i++) + { + adjusted[i] = weights[i] / sum; + } + } + + return adjusted; + } + + private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache) + { + if (parameters.SessionExpirationDuration == 0) + parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration; + + if (parameters.ErrorThreshold == 0) + parameters.ErrorThreshold = defaultErrorThreshold; + + if (parameters.ClientRebalanceInterval <= 0) + parameters.ClientRebalanceInterval = defaultRebalanceInterval; + + if (parameters.GracefulCloseOnSwitchTimeout <= 0) + parameters.GracefulCloseOnSwitchTimeout = defaultGracefulCloseOnSwitchTimeout; + + if (parameters.HealthcheckTimeout <= 0) + parameters.HealthcheckTimeout = defaultHealthcheckTimeout; + + if (parameters.NodeDialTimeout <= 0) + parameters.NodeDialTimeout = defaultDialTimeout; + + if (parameters.NodeStreamTimeout <= 0) + parameters.NodeStreamTimeout = defaultStreamTimeout; + + if (cache.TokenDuration == 0) + cache.TokenDuration = defaultSessionTokenExpirationDuration; + + parameters.ClientBuilder ??= new Func((address) => + { + var wrapperPrm = new WrapperPrm + { + Address = address, + Key = parameters.Key, + Logger = parameters.Logger, + DialTimeout = parameters.NodeDialTimeout, + StreamTimeout = parameters.NodeStreamTimeout, + ErrorThreshold = parameters.ErrorThreshold, + GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout + }; + + return new ClientWrapper(wrapperPrm); + } + ); + } + + private FrostFSClient? Сonnection() + { + foreach (var pool in InnerPools!) + { + var client = pool.Connection(); + if (client != null) + { + return client.Client; + } + } + + return null; + } + + private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) + { + var client = cw.Client; + var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false); + + var epoch = networkInfo.Epoch; + + ulong exp = ulong.MaxValue - epoch < duration + ? ulong.MaxValue + : epoch + duration; + + var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx }; + + return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false); + } + + private static string FormCacheKey(string address, ECDsa key, bool clientCut) + { + var k = key.PrivateKey; + var stype = clientCut ? "client" : "server"; + + return $"{address}{stype}{k}"; + } + + public void Close() + { + CancellationTokenSource.Cancel(); + + if (InnerPools != null) + { + // close all clients + foreach (var innerPool in InnerPools) + foreach (var client in innerPool.Clients) + if (client.StatusMonitor.IsDialed()) + client.Client?.Close(); + } + } + + // startRebalance runs loop to monitor connection healthy status. + internal void StartRebalance(CallContext ctx) + { + var buffers = new double[RebalanceParams.NodesParams.Length][]; + + for (int i = 0; i < RebalanceParams.NodesParams.Length; i++) + { + var parameters = this.RebalanceParams.NodesParams[i]; + buffers[i] = new double[parameters.Weights.Count]; + + Task.Run(async () => + { + await Task.Delay((int)RebalanceParams.ClientRebalanceInterval).ConfigureAwait(false); + UpdateNodesHealth(ctx, buffers); + }); + } + } + + private void UpdateNodesHealth(CallContext ctx, double[][] buffers) + { + var tasks = new Task[InnerPools!.Length]; + + for (int i = 0; i < InnerPools.Length; i++) + { + var bufferWeights = buffers[i]; + + tasks[i] = Task.Run(() => UpdateInnerNodesHealth(ctx, i, bufferWeights)); + } + + Task.WaitAll(tasks); + } + + private async ValueTask UpdateInnerNodesHealth(CallContext ctx, int poolIndex, double[] bufferWeights) + { + if (poolIndex > InnerPools!.Length - 1) + { + return; + } + + var pool = InnerPools[poolIndex]; + + var options = RebalanceParams; + + int healthyChanged = 0; + + var tasks = new Task[pool.Clients.Length]; + + for (int j = 0; j < pool.Clients.Length; j++) + { + var client = pool.Clients[j]; + var healthy = false; + string? error = null; + var changed = false; + + try + { + // check timeout settings + changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false); + healthy = true; + bufferWeights[j] = options.NodesParams[poolIndex].Weights[j]; + } + catch (FrostFsException e) + { + error = e.Message; + bufferWeights[j] = 0; + + Cache.DeleteByPrefix(client.StatusMonitor.Address); + } + + if (changed) + { + StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}"); + if (string.IsNullOrEmpty(error)) + { + fields.Append($", reason {error}"); + Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString()); + + Interlocked.Exchange(ref healthyChanged, 1); + } + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + if (Interlocked.CompareExchange(ref healthyChanged, -1, -1) == 1) + { + var probabilities = AdjustWeights(bufferWeights); + + lock (_lock) + { + pool.Sampler = new Sampler(probabilities); + } + } + } + } + + private bool CheckSessionTokenErr(Exception error, string address) + { + if (error == null) + { + return false; + } + + if (error is SessionNotFoundException || error is SessionExpiredException) + { + this.Cache.DeleteByPrefix(address); + return true; + } + + return false; + } + + public Statistic Statistic() + { + if (InnerPools == null) + { + throw new InvalidObjectException(nameof(Pool)); + } + + var statistics = new Statistic(); + + foreach (var inner in InnerPools) + { + int nodeIndex = 0; + int valueIndex = 0; + var nodes = new string[inner.Clients.Length]; + + lock (_lock) + { + foreach (var client in inner.Clients) + { + if (client.StatusMonitor.IsHealthy()) + { + nodes[valueIndex++] = client.StatusMonitor.Address; + } + + var node = new NodeStatistic + { + Address = client.StatusMonitor.Address, + Methods = client.StatusMonitor.MethodsStatus(), + OverallErrors = client.StatusMonitor.GetOverallErrorRate(), + CurrentErrors = client.StatusMonitor.GetCurrentErrorRate() + }; + + statistics.Nodes[nodeIndex++] = node; + + statistics.OverallErrors += node.OverallErrors; + } + + if (statistics.CurrentNodes == null || statistics.CurrentNodes.Length == 0) + { + statistics.CurrentNodes = nodes; + } + } + } + + return statistics; + } + + public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false); + } + + public async Task GetNodeInfoAsync(PrmNodeInfo? args = null) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetNodeInfoAsync(args).ConfigureAwait(false); + } + + public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false); + } + + public async Task CreateSessionAsync(PrmSessionCreate args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.CreateSessionAsync(args).ConfigureAwait(false); + } + + public async Task AddChainAsync(PrmApeChainAdd args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.AddChainAsync(args).ConfigureAwait(false); + } + + public async Task RemoveChainAsync(PrmApeChainRemove args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + await client.RemoveChainAsync(args).ConfigureAwait(false); + } + + public async Task ListChainAsync(PrmApeChainList args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.ListChainAsync(args).ConfigureAwait(false); + } + + public async Task GetContainerAsync(PrmContainerGet args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetContainerAsync(args).ConfigureAwait(false); + } + + public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return client.ListContainersAsync(args); + } + + public async Task CreateContainerAsync(PrmContainerCreate args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.CreateContainerAsync(args).ConfigureAwait(false); + } + + public async Task DeleteContainerAsync(PrmContainerDelete args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + await client.DeleteContainerAsync(args).ConfigureAwait(false); + } + + public async Task GetObjectHeadAsync(PrmObjectHeadGet args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetObjectHeadAsync(args).ConfigureAwait(false); + } + + public async Task GetObjectAsync(PrmObjectGet args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetObjectAsync(args).ConfigureAwait(false); + } + + public async Task PutObjectAsync(PrmObjectPut args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.PutObjectAsync(args).ConfigureAwait(false); + } + + public async Task PutSingleObjectAsync(PrmSingleObjectPut args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.PutSingleObjectAsync(args).ConfigureAwait(false); + } + + public async Task DeleteObjectAsync(PrmObjectDelete args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + await client.DeleteObjectAsync(args).ConfigureAwait(false); + } + + public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return client.SearchObjectsAsync(args); + } + + public async Task GetBalanceAsync(PrmBalance? args = null) + { + var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); + return await client.GetBalanceAsync(args).ConfigureAwait(false); + } + + public bool RestartIfUnhealthy(CallContext ctx) + { + throw new NotImplementedException(); + } + + public bool IsHealthy() + { + throw new NotImplementedException(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + Close(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) + { + throw new NotImplementedException(); + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs new file mode 100644 index 0000000..b1bc9b9 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs @@ -0,0 +1,16 @@ +namespace FrostFS.SDK.ClientV2; + +public class RebalanceParameters( + NodesParam[] nodesParams, + ulong nodeRequestTimeout, + ulong clientRebalanceInterval, + ulong sessionExpirationDuration) +{ + public NodesParam[] NodesParams { get; set; } = nodesParams; + + public ulong NodeRequestTimeout { get; set; } = nodeRequestTimeout; + + public ulong ClientRebalanceInterval { get; set; } = clientRebalanceInterval; + + public ulong SessionExpirationDuration { get; set; } = sessionExpirationDuration; +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs b/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs new file mode 100644 index 0000000..9eb931f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs @@ -0,0 +1,14 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +// RequestInfo groups info about pool request. +struct RequestInfo +{ + public string Address { get; set; } + + public MethodIndex MethodIndex { get; set; } + + public TimeSpan Elapsed { get; set; } +} + diff --git a/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs b/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs new file mode 100644 index 0000000..0d4a1e0 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs @@ -0,0 +1,85 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +internal sealed class Sampler +{ + private readonly object _lock = new(); + + private Random random = new(); + + internal double[] Probabilities { get; set; } + internal int[] Alias { get; set; } + + internal Sampler(double[] probabilities) + { + var small = new WorkList(); + var large = new WorkList(); + + var n = probabilities.Length; + + // sampler.randomGenerator = rand.New(source) + Probabilities = new double[n]; + Alias = new int[n]; + + // Compute scaled probabilities. + var p = new double[n]; + + for (int i = 0; i < n; i++) + { + p[i] = probabilities[i] * n; + if (p[i] < 1) + small.Add(i); + else + large.Add(i); + } + + while (small.Length > 0 && large.Length > 0) + { + var l = small.Remove(); + var g = large.Remove(); + + Probabilities[l] = p[l]; + Alias[l] = g; + + p[g] = p[g] + p[l] - 1; + + if (p[g] < 1) + small.Add(g); + else + large.Add(g); + } + + while (large.Length > 0) + { + var g = large.Remove(); + Probabilities[g] = 1; + } + + while (small.Length > 0) + { + var l = small.Remove(); + probabilities[l] = 1; + } + } + + internal int Next() + { + var n = Alias.Length; + + int i; + double f; + lock (_lock) + { + i = random.Next(0, n - 1); + f = random.NextDouble(); + } + + if (f < Probabilities[i]) + { + return i; + } + + return Alias[i]; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs new file mode 100644 index 0000000..eccb0a5 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections; + +namespace FrostFS.SDK.ClientV2; + +internal struct SessionCache +{ + public SessionCache(ulong sessionExpirationDuration) + { + TokenDuration = sessionExpirationDuration; + } + + internal Hashtable Cache { get; } = []; + + internal ulong CurrentEpoch { get; set; } + + internal ulong TokenDuration { get; set; } + + internal void DeleteByPrefix(string prefix) + { + foreach (var key in Cache.Keys) + { + if (((string)key).StartsWith(prefix, StringComparison.Ordinal)) + { + Cache.Remove(key); + } + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs b/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs new file mode 100644 index 0000000..58fa72d --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs @@ -0,0 +1,12 @@ +using System.Collections.ObjectModel; + +namespace FrostFS.SDK.ClientV2; + +public sealed class Statistic +{ + public ulong OverallErrors { get; internal set; } + + public Collection Nodes { get; } = []; + + public string[]? CurrentNodes { get; internal set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs b/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs new file mode 100644 index 0000000..9434cb9 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.ClientV2; + +public class StatusSnapshot() +{ + public ulong AllTime { get; internal set; } + + public ulong AllRequests { get; internal set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs b/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs new file mode 100644 index 0000000..39551f4 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; + +namespace FrostFS.SDK.ClientV2; + +internal sealed class WorkList +{ + private readonly List elements = []; + + internal int Length + { + get { return elements.Count; } + } + + internal void Add(int element) + { + elements.Add(element); + } + + internal int Remove() + { + int last = elements.LastOrDefault(); + elements.RemoveAt(elements.Count - 1); + return last; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs b/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs new file mode 100644 index 0000000..b389f68 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs @@ -0,0 +1,34 @@ +using System; +using System.Security.Cryptography; + +using Grpc.Net.Client; + +using Microsoft.Extensions.Logging; + +namespace FrostFS.SDK.ClientV2; + +// wrapperPrm is params to create clientWrapper. +[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] +public struct WrapperPrm +{ + internal ILogger? Logger { get; set; } + + internal string Address { get; set; } + + internal ECDsa? Key { get; set; } + + internal ulong DialTimeout { get; set; } + + internal ulong StreamTimeout { get; set; } + + internal uint ErrorThreshold { get; set; } + + internal Action ResponseInfoCallback { get; set; } + + internal Action PoolRequestInfoCallback { get; set; } + + internal GrpcChannelOptions GrpcChannelOptions { get; set; } + + internal ulong GracefulCloseOnSwitchTimeout { get; set; } +} + diff --git a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs new file mode 100644 index 0000000..94dda54 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; + +using FrostFS.Accounting; + +namespace FrostFS.SDK.ClientV2; + +internal sealed class AccountingServiceProvider : ContextAccessor +{ + private readonly AccountingService.AccountingServiceClient? _accountingServiceClient; + + internal AccountingServiceProvider( + AccountingService.AccountingServiceClient? accountingServiceClient, + EnvironmentContext context) + : base(context) + { + _accountingServiceClient = accountingServiceClient; + } + + internal async Task GetBallance(PrmBalance args) + { + var ctx = args.Context!; + + BalanceRequest request = new() + { + Body = new() + { + OwnerId = ctx.OwnerId!.OwnerID + } + }; + + request.AddMetaHeader(args.XHeaders); + request.Sign(ctx.Key!); + + var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + return response.Body.Balance; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs similarity index 90% rename from src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs rename to src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs index ea92329..1c5ab8c 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs @@ -3,13 +3,13 @@ using System.Threading.Tasks; using Frostfs.V2.Ape; using Frostfs.V2.Apemanager; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.ClientV2.Services; internal sealed class ApeManagerServiceProvider : ContextAccessor { private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient; - internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientEnvironment context) + internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context) : base(context) { _apeManagerServiceClient = apeManagerServiceClient; @@ -18,7 +18,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task AddChainAsync(PrmApeChainAdd args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -45,7 +45,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task RemoveChainAsync(PrmApeChainRemove args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -70,7 +70,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task ListChainAsync(PrmApeChainList args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index c42b2ba..14a3e2d 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -12,11 +12,11 @@ using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientEnvironment context) : ContextAccessor(context), ISessionProvider +internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider { - readonly SessionProvider sessions = new(context); + readonly SessionProvider sessions = new(envCtx); - public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } @@ -35,8 +35,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; - ctx.OwnerId ??= Context.Owner; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.OwnerId ??= EnvironmentContext.Owner; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -147,7 +147,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Verifier.CheckResponse(response); } - private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, Context ctx) + private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx) { if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -172,7 +172,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Removed } - private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, Context ctx) + private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx) { var request = GetContainerRequest(id, null, ctx); diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 91cca73..91645fb 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -12,27 +12,33 @@ internal sealed class NetmapServiceProvider : ContextAccessor { private readonly NetmapService.NetmapServiceClient netmapServiceClient; - internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientEnvironment context) + internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context) : base(context) { this.netmapServiceClient = netmapServiceClient; } - internal async Task GetNetworkSettingsAsync(Context ctx) + internal async Task GetNetworkSettingsAsync(CallContext ctx) { - if (Context.NetworkSettings != null) - return Context.NetworkSettings; + if (EnvironmentContext.NetworkSettings != null) + return EnvironmentContext.NetworkSettings; - var info = await GetNetworkInfoAsync(ctx).ConfigureAwait(false); + var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false); var settings = new NetworkSettings(); - foreach (var param in info.Body.NetworkInfo.NetworkConfig.Parameters) + var info = response.Body.NetworkInfo; + + settings.Epoch = info.CurrentEpoch; + settings.MagicNumber = info.MagicNumber; + settings.MsPerBlock = info.MsPerBlock; + + foreach (var param in info.NetworkConfig.Parameters) { SetNetworksParam(param, settings); } - Context.NetworkSettings = settings; + EnvironmentContext.NetworkSettings = settings; return settings; } @@ -40,7 +46,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -53,6 +59,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); + + var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -60,9 +68,9 @@ internal sealed class NetmapServiceProvider : ContextAccessor return response.Body.ToModel(); } - internal async Task GetNetworkInfoAsync(Context ctx) + internal async Task GetNetworkInfoAsync(CallContext ctx) { - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -83,7 +91,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index dce9787..6ab3362 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -20,14 +20,14 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider private readonly SessionProvider sessions; private ObjectService.ObjectServiceClient client; - internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientEnvironment env) + internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env) : base(env) { - this.sessions = new(Context); + this.sessions = new(EnvironmentContext); this.client = client; } - public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } @@ -35,7 +35,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -74,7 +74,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -108,7 +108,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; - ctx.Key ??= Context.Key?.ECDsaKey; + ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; if (ctx.Key == null) throw new InvalidObjectException(nameof(ctx.Key)); @@ -238,7 +238,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider var ctx = args.Context!; var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false); - var token = new FrostFsSessionToken(tokenRaw.Serialize()); + var token = new FrostFsSessionToken(tokenRaw.Serialize(), tokenRaw.Body.Id.ToUuid()); args.SessionToken = token; @@ -254,7 +254,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider if (args.MaxObjectSizeCache == 0) { - var networkSettings = await Context.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }) + var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }) .ConfigureAwait(false); args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; @@ -352,7 +352,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider } else { - chunkBuffer = Context.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); isRentBuffer = true; } @@ -404,7 +404,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider } } - private async Task GetUploadStream(PrmObjectPut args, Context ctx) + private async Task GetUploadStream(PrmObjectPut args, CallContext ctx) { var header = args.Header!; @@ -449,7 +449,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider return await PutObjectInit(initRequest, ctx).ConfigureAwait(false); } - private async Task GetObject(GetRequest request, Context ctx) + private async Task GetObject(GetRequest request, CallContext ctx) { var reader = GetObjectInit(request, ctx); @@ -461,7 +461,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider return modelObject; } - private ObjectReader GetObjectInit(GetRequest initRequest, Context ctx) + private ObjectReader GetObjectInit(GetRequest initRequest, CallContext ctx) { if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); @@ -471,7 +471,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider return new ObjectReader(call); } - private async Task PutObjectInit(PutRequest initRequest, Context ctx) + private async Task PutObjectInit(PutRequest initRequest, CallContext ctx) { if (initRequest is null) { @@ -485,7 +485,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider return new ObjectStreamer(call); } - private async IAsyncEnumerable SearchObjects(SearchRequest request, Context ctx) + private async IAsyncEnumerable SearchObjects(SearchRequest request, CallContext ctx) { using var stream = GetSearchReader(request, ctx); @@ -503,7 +503,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider } } - private SearchReader GetSearchReader(SearchRequest initRequest, Context ctx) + private SearchReader GetSearchReader(SearchRequest initRequest, CallContext ctx) { if (initRequest is null) { diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index de22165..1dfe53f 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor { private readonly SessionService.SessionServiceClient? _sessionServiceClient; - internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientEnvironment context) + internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context) : base(context) { _sessionServiceClient = sessionServiceClient; @@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor { var ctx = args.Context!; - ctx.OwnerId ??= Context.Owner; + ctx.OwnerId ??= EnvironmentContext.Owner; var request = new CreateRequest { @@ -37,7 +37,7 @@ internal sealed class SessionServiceProvider : ContextAccessor return await CreateSession(request, args.Context!).ConfigureAwait(false); } - internal async Task CreateSession(CreateRequest request, Context ctx) + internal async Task CreateSession(CreateRequest request, CallContext ctx) { var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs index 05797de..3fb7674 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -internal class ContextAccessor(ClientEnvironment context) +internal class ContextAccessor(EnvironmentContext context) { - protected ClientEnvironment Context { get; set; } = context; + protected EnvironmentContext EnvironmentContext { get; set; } = context; } diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index 8d0d1f5..51fe337 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -4,16 +4,17 @@ namespace FrostFS.SDK.ClientV2; internal interface ISessionProvider { - ValueTask GetOrCreateSession(ISessionToken args, Context ctx); + ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx); } -internal sealed class SessionProvider(ClientEnvironment env) +internal sealed class SessionProvider(EnvironmentContext envCtx) { - public async ValueTask GetOrCreateSession(ISessionToken args, Context ctx) + // TODO: implement cache for session in the next iteration + public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { if (args.SessionToken is null) { - return await env.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }) + return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }) .ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs b/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs rename to src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs index 11023e7..d0596c9 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientEnvironment.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs @@ -6,7 +6,7 @@ using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2; -public class ClientEnvironment(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable +public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { private ArrayPool? _arrayPool; diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs index d06ce03..524f358 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs @@ -4,6 +4,10 @@ namespace FrostFS.SDK.ClientV2; public class NetworkSettings { + public ulong Epoch { get; internal set; } + public ulong MagicNumber { get; internal set; } + public long MsPerBlock { get; internal set; } + public ulong AuditFee { get; internal set; } public ulong BasicIncomeRate { get; internal set; } public ulong ContainerFee { get; internal set; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index 0303626..f4d659c 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -11,7 +11,7 @@ namespace FrostFS.SDK.ClientV2; internal static class ObjectTools { - internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, Context ctx) + internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) { var grpcHeader = CreateHeader(header, [], ctx); @@ -21,7 +21,7 @@ internal static class ObjectTools return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal static Object.Object CreateObject(FrostFsObject @object, Context ctx) + internal static Object.Object CreateObject(FrostFsObject @object, CallContext ctx) { @object.Header.OwnerId ??= ctx.OwnerId; @object.Header.Version ??= ctx.Version; @@ -53,7 +53,7 @@ internal static class ObjectTools return obj; } - internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, Context ctx) + internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, CallContext ctx) { if (split == null) return; @@ -85,7 +85,7 @@ internal static class ObjectTools grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, Context ctx) + internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, CallContext ctx) { header.OwnerId ??= ctx.OwnerId; header.Version ??= ctx.Version; diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs index 13d87b0..f1006a4 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs @@ -76,6 +76,11 @@ public static class RequestSigner public static byte[] SignData(this ECDsa key, byte[] data) { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + var hash = new byte[65]; hash[0] = 0x04; diff --git a/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs new file mode 100644 index 0000000..af77424 --- /dev/null +++ b/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs @@ -0,0 +1,62 @@ +using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.Session; + +using Google.Protobuf; + +namespace FrostFS.Accounting; + +public partial class BalanceRequest : IRequest +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} + +public partial class BalanceResponse : IResponse +{ + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index e58e18c..8f4e868 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,49 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Google.Protobuf; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests; -public abstract class ContainerTestsBase -{ - protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - - protected IOptions Settings { get; set; } - protected ContainerMocker Mocker { get; set; } - - protected ContainerTestsBase() - { - Settings = Options.Create(new SingleOwnerClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); - - Mocker = new ContainerMocker(this.key) - { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), - Version = new FrostFsVersion(2, 13), - ContainerGuid = Guid.NewGuid() - }; - } - - protected IFrostFSClient GetClient() - { - return ClientV2.FrostFSClient.GetTestInstance( - Settings, - null, - new NetworkMocker(this.key).GetMock().Object, - new SessionMocker(this.key).GetMock().Object, - Mocker.GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); - } -} - +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class ContainerTest : ContainerTestsBase { [Fact] diff --git a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs new file mode 100644 index 0000000..f9bfc9a --- /dev/null +++ b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs @@ -0,0 +1,40 @@ +using FrostFS.SDK.ClientV2.Interfaces; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public abstract class ContainerTestsBase +{ + internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + protected ContainerMocker Mocker { get; set; } + + protected ContainerTestsBase() + { + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + Mocker = new ContainerMocker(this.key) + { + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13), + ContainerGuid = Guid.NewGuid() + }; + } + + protected IFrostFSClient GetClient() + { + return ClientV2.FrostFSClient.GetTestInstance( + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} diff --git a/src/FrostFS.SDK.Tests/GlobalSuppressions.cs b/src/FrostFS.SDK.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..d1eb84b --- /dev/null +++ b/src/FrostFS.SDK.Tests/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + + diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs index 958a73e..9f5d824 100644 --- a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs @@ -12,7 +12,9 @@ public class MetricsInterceptor() : Interceptor ClientInterceptorContext context, AsyncUnaryCallContinuation continuation) { - var call = continuation(request, context); + ArgumentNullException.ThrowIfNull(continuation); + + using var call = continuation(request, context); return new AsyncUnaryCall( HandleUnaryResponse(call), @@ -27,7 +29,7 @@ public class MetricsInterceptor() : Interceptor var watch = new Stopwatch(); watch.Start(); - var response = await call.ResponseAsync; + var response = await call.ResponseAsync.ConfigureAwait(false); watch.Stop(); diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 22172f2..63e3d64 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -26,8 +26,11 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) OwnerId = objectHeader.OwnerId!.ToMessage() }; - foreach (var attr in objectHeader.Attributes) - header.Attributes.Add(attr.ToMessage()); + if (objectHeader.Attributes != null) + { + foreach (var attr in objectHeader.Attributes) + header.Attributes.Add(attr.ToMessage()); + } var response = new GetResponse { diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs index 9836d37..f386468 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + using FrostFS.SDK.ProtosV2.Interfaces; using Grpc.Core; @@ -6,13 +8,15 @@ namespace FrostFS.SDK.Tests; public class ClientStreamWriter : IClientStreamWriter { - public List Messages { get; set; } = []; + private WriteOptions? _options; + + public Collection Messages { get; } = []; public bool CompletedTask { get; private set; } public WriteOptions? WriteOptions { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); + get => _options; + set => _options = value; } public Task CompleteAsync() diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index ba34cde..e727cfb 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -25,7 +25,10 @@ public abstract class ServiceBase(string key) public static FrostFsVersion DefaultVersion { get; } = new(2, 13); public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); - public Metadata Metadata { get; protected set; } +#pragma warning disable CA2227 // this is specific object, should be treated as is + public Metadata? Metadata { get; set; } +#pragma warning restore CA2227 + public DateTime? DateTime { get; protected set; } public CancellationToken CancellationToken { get; protected set; } @@ -35,6 +38,8 @@ public abstract class ServiceBase(string key) protected ResponseVerificationHeader GetResponseVerificationHeader(IResponse response) { + ArgumentNullException.ThrowIfNull(response); + var verifyHeader = new ResponseVerificationHeader { MetaSignature = new Refs.Signature diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index d77322a..aa4d048 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; + using FrostFS.Container; using FrostFS.Refs; using FrostFS.SDK.ClientV2; @@ -41,7 +43,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) putResponse.VerifyHeader = GetResponseVerificationHeader(putResponse); var metadata = new Metadata(); - var putContainerResponse = new AsyncUnaryCall( + using var putContainerResponse = new AsyncUnaryCall( Task.FromResult(putResponse), Task.FromResult(metadata), () => new Grpc.Core.Status(StatusCode.OK, string.Empty), @@ -180,7 +182,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) public bool ReturnContainerRemoved { get; set; } - public List ContainerIds { get; set; } = []; + public Collection ContainerIds { get; } = []; - public List> Requests { get; set; } = []; + public Collection> Requests { get; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs index 72b3aab..003d7ac 100644 --- a/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs +++ b/src/FrostFS.SDK.Tests/Mocks/NetworkMocker.cs @@ -25,18 +25,17 @@ public class NetworkMocker(string key) : ServiceBase(key) "MaintenanceModeAllowed" ]; - public Dictionary? Parameters { get; set; } + public Dictionary Parameters { get; } = []; - public LocalNodeInfoResponse NodeInfoResponse { get; set; } + public LocalNodeInfoResponse? NodeInfoResponse { get; set; } - public LocalNodeInfoRequest LocalNodeInfoRequest { get; set; } + public LocalNodeInfoRequest? LocalNodeInfoRequest { get; set; } - public NetworkInfoRequest NetworkInfoRequest { get; set; } + public NetworkInfoRequest? NetworkInfoRequest { get; set; } - public NetmapSnapshotResponse NetmapSnapshotResponse { get; set; } - - public NetmapSnapshotRequest NetmapSnapshotRequest { get; set; } + public NetmapSnapshotResponse? NetmapSnapshotResponse { get; set; } + public NetmapSnapshotRequest? NetmapSnapshotRequest { get; set; } public Mock GetMock() { diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 1dc9152..d0b900d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Security.Cryptography; using FrostFS.Object; @@ -92,7 +93,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) } - if (ResultObjectIds != null) + if (ResultObjectIds != null && ResultObjectIds.Count > 0) { PutResponse putResponse = new() { @@ -197,14 +198,14 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) public Header? HeadResponse { get; set; } - public List? ResultObjectIds { get; set; } + public Collection? ResultObjectIds { get; } = []; public ClientStreamWriter? ClientStreamWriter { get; private set; } = new(); - public List PutSingleRequests { get; private set; } = []; + public Collection PutSingleRequests { get; private set; } = []; - public List DeleteRequests { get; private set; } = []; + public Collection DeleteRequests { get; private set; } = []; - public List HeadRequests { get; private set; } = []; + public Collection HeadRequests { get; private set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index 359f10b..88ed60d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -8,13 +8,14 @@ using Moq; namespace FrostFS.SDK.Tests; +[System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class SessionMocker(string key) : ServiceBase(key) { public byte[]? SessionId { get; set; } public byte[]? SessionKey { get; set; } - public CreateRequest CreateSessionRequest { get; private set; } + public CreateRequest? CreateSessionRequest { get; private set; } public Mock GetMock() { @@ -24,7 +25,7 @@ public class SessionMocker(string key) : ServiceBase(key) if (SessionId == null) { - SessionId = new byte[32]; + SessionId = new byte[16]; rand.NextBytes(SessionId); } diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 0079d82..f09cafb 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -1,55 +1,13 @@ -using System.Security.Cryptography; +using System.Diagnostics.CodeAnalysis; using FrostFS.Netmap; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.Cryptography; using Google.Protobuf; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests; -public abstract class NetworkTestsBase -{ - protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - - protected IOptions Settings { get; set; } - - protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13); - - protected ECDsa ECDsaKey { get; set; } - protected FrostFsOwner OwnerId { get; set; } - protected NetworkMocker Mocker { get; set; } - - protected NetworkTestsBase() - { - Settings = Options.Create(new SingleOwnerClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); - - ECDsaKey = key.LoadWif(); - OwnerId = FrostFsOwner.FromKey(ECDsaKey); - - Mocker = new NetworkMocker(this.key); - } - - protected IFrostFSClient GetClient() - { - return ClientV2.FrostFSClient.GetTestInstance( - Settings, - null, - Mocker.GetMock().Object, - new SessionMocker(this.key).GetMock().Object, - new ContainerMocker(this.key).GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); - } -} - +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class NetworkTest : NetworkTestsBase { [Theory] @@ -57,27 +15,24 @@ public class NetworkTest : NetworkTestsBase [InlineData(true)] public async void NetworkSettingsTest(bool useContext) { - Mocker.Parameters = new Dictionary - { - { "AuditFee", [1] }, - { "BasicIncomeRate", [2] }, - { "ContainerFee", [3] }, - { "ContainerAliasFee", [4] }, - { "EpochDuration", [5] }, - { "InnerRingCandidateFee", [6] }, - { "MaxECDataCount", [7] }, - { "MaxECParityCount", [8] }, - { "MaxObjectSize", [9] }, - { "WithdrawFee", [10] }, - { "HomomorphicHashingDisabled", [1] }, - { "MaintenanceModeAllowed", [1] }, - }; + Mocker.Parameters.Add("AuditFee", [1]); + Mocker.Parameters.Add("BasicIncomeRate", [2]); + Mocker.Parameters.Add("ContainerFee", [3]); + Mocker.Parameters.Add("ContainerAliasFee", [4]); + Mocker.Parameters.Add("EpochDuration", [5]); + Mocker.Parameters.Add("InnerRingCandidateFee", [6]); + Mocker.Parameters.Add("MaxECDataCount", [7]); + Mocker.Parameters.Add("MaxECParityCount", [8]); + Mocker.Parameters.Add("MaxObjectSize", [9]); + Mocker.Parameters.Add("WithdrawFee", [10]); + Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]); + Mocker.Parameters.Add("MaintenanceModeAllowed", [1]); var param = new PrmNetworkSettings(); if (useContext) { - param.Context = new Context + param.Context = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -119,6 +74,7 @@ public class NetworkTest : NetworkTestsBase } else { + Assert.NotNull(Mocker.NetworkInfoRequest); Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders); Assert.Null(Mocker.DateTime); } @@ -127,6 +83,7 @@ public class NetworkTest : NetworkTestsBase [Theory] [InlineData(false)] [InlineData(true)] + public async void NetmapSnapshotTest(bool useContext) { var body = new NetmapSnapshotResponse.Types.Body @@ -164,7 +121,7 @@ public class NetworkTest : NetworkTestsBase if (useContext) { param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new Context + param.Context = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -210,6 +167,7 @@ public class NetworkTest : NetworkTestsBase if (useContext) { + Assert.NotNull(Mocker.NetmapSnapshotRequest); Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key); Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value); @@ -222,6 +180,7 @@ public class NetworkTest : NetworkTestsBase } else { + Assert.NotNull(Mocker.NetmapSnapshotRequest); Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); Assert.Null(Mocker.DateTime); } @@ -254,7 +213,7 @@ public class NetworkTest : NetworkTestsBase if (useContext) { param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new Context + param.Context = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -282,6 +241,7 @@ public class NetworkTest : NetworkTestsBase Assert.Equal("value1", result.Attributes["key1"]); Assert.Equal("value2", result.Attributes["key2"]); + Assert.NotNull(Mocker.LocalNodeInfoRequest); if (useContext) { Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); diff --git a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs new file mode 100644 index 0000000..071b425 --- /dev/null +++ b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs @@ -0,0 +1,48 @@ +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; + +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +public abstract class NetworkTestsBase +{ + internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + + protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13); + + protected ECDsa ECDsaKey { get; set; } + protected FrostFsOwner OwnerId { get; set; } + protected NetworkMocker Mocker { get; set; } + + protected NetworkTestsBase() + { + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + ECDsaKey = key.LoadWif(); + OwnerId = FrostFsOwner.FromKey(ECDsaKey); + + Mocker = new NetworkMocker(this.key); + } + + protected IFrostFSClient GetClient() + { + return ClientV2.FrostFSClient.GetTestInstance( + Settings, + null, + Mocker.GetMock().Object, + new SessionMocker(this.key).GetMock().Object, + new ContainerMocker(this.key).GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index af58346..07fa8e9 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -1,71 +1,19 @@ +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; using FrostFS.Refs; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ClientV2; using FrostFS.SDK.Cryptography; using Google.Protobuf; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests; -public abstract class ObjectTestsBase -{ - protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - - protected IOptions Settings { get; set; } - protected FrostFsContainerId ContainerId { get; set; } - - protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); - protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key); - protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key); - protected ObjectMocker Mocker { get; set; } - - protected ObjectTestsBase() - { - var ecdsaKey = key.LoadWif(); - - Settings = Options.Create(new SingleOwnerClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); - - Mocker = new ObjectMocker(key) - { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), - Version = new FrostFsVersion(2, 13), - ContainerGuid = Guid.NewGuid() - }; - - ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - - Mocker.ObjectHeader = new( - ContainerId, - FrostFsObjectType.Regular, - [new FrostFsAttributePair("k", "v")], - null, - FrostFsOwner.FromKey(ecdsaKey), - new FrostFsVersion(2, 13)); - } - - protected IFrostFSClient GetClient() - { - return FrostFSClient.GetTestInstance( - Settings, - null, - NetworkMocker.GetMock().Object, - SessionMocker.GetMock().Object, - ContainerMocker.GetMock().Object, - Mocker.GetMock().Object); - } -} - +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class ObjectTest : ObjectTestsBase { [Fact] @@ -75,7 +23,7 @@ public class ObjectTest : ObjectTestsBase var ecdsaKey = key.LoadWif(); - var ctx = new Context + var ctx = new CallContext { Key = ecdsaKey, OwnerId = FrostFsOwner.FromKey(ecdsaKey), @@ -88,18 +36,21 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(result); - Assert.Equal(Mocker.ObjectHeader!.ContainerId.GetValue(), result.Header.ContainerId.GetValue()); - Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, result.Header.OwnerId!.Value); + Assert.NotNull(Mocker.ObjectHeader); + + Assert.Equal(Mocker.ObjectHeader.ContainerId.GetValue(), result.Header.ContainerId.GetValue()); + Assert.Equal(Mocker.ObjectHeader.OwnerId!.Value, result.Header.OwnerId!.Value); Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); + Assert.NotNull(result.Header.Attributes); Assert.Single(result.Header.Attributes); - Assert.Equal(Mocker.ObjectHeader.Attributes[0].Key, result.Header.Attributes[0].Key); - Assert.Equal(Mocker.ObjectHeader.Attributes[0].Value, result.Header.Attributes[0].Value); + Assert.Equal(Mocker.ObjectHeader.Attributes![0].Key, result.Header.Attributes[0].Key); + Assert.Equal(Mocker.ObjectHeader.Attributes![0].Value, result.Header.Attributes[0].Value); } [Fact] public async void PutObjectTest() { - Mocker.ResultObjectIds = new([SHA256.HashData([])]); + Mocker.ResultObjectIds.Add(SHA256.HashData([])); Random rnd = new(); var bytes = new byte[1024]; @@ -134,7 +85,7 @@ public class ObjectTest : ObjectTestsBase [Fact] public async void ClientCutTest() { - NetworkMocker.Parameters = new Dictionary() { { "MaxObjectSize", [0x0, 0xa] } }; + NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]); var blockSize = 2560; byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); @@ -150,17 +101,19 @@ public class ObjectTest : ObjectTestsBase Random rnd = new(); - List objIds = new([new byte[32], new byte[32], new byte[32]]); + Collection objIds = new([new byte[32], new byte[32], new byte[32]]); rnd.NextBytes(objIds.ElementAt(0)); rnd.NextBytes(objIds.ElementAt(1)); rnd.NextBytes(objIds.ElementAt(2)); - Mocker.ResultObjectIds = objIds; + foreach (var objId in objIds) + Mocker.ResultObjectIds.Add(objId); var result = await GetClient().PutObjectAsync(param); var singleObjects = Mocker.PutSingleRequests.ToArray(); + Assert.NotNull(Mocker.ClientStreamWriter?.Messages); var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray(); Assert.Single(singleObjects); @@ -262,6 +215,7 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(FrostFsObjectType.Regular, response.ObjectType); + Assert.NotNull(response.Attributes); Assert.Single(response.Attributes); Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key); diff --git a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs new file mode 100644 index 0000000..543bda6 --- /dev/null +++ b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs @@ -0,0 +1,59 @@ +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public abstract class ObjectTestsBase +{ + protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + protected FrostFsContainerId ContainerId { get; set; } + + protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); + protected SessionMocker SessionMocker { get; set; } = new SessionMocker(key); + protected ContainerMocker ContainerMocker { get; set; } = new ContainerMocker(key); + protected ObjectMocker Mocker { get; set; } + + protected ObjectTestsBase() + { + var ecdsaKey = key.LoadWif(); + + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + Mocker = new ObjectMocker(key) + { + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13), + ContainerGuid = Guid.NewGuid() + }; + + ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + + Mocker.ObjectHeader = new( + ContainerId, + FrostFsObjectType.Regular, + [new FrostFsAttributePair("k", "v")], + null, + FrostFsOwner.FromKey(ecdsaKey), + new FrostFsVersion(2, 13)); + } + + protected IFrostFSClient GetClient() + { + return FrostFSClient.GetTestInstance( + Settings, + null, + NetworkMocker.GetMock().Object, + SessionMocker.GetMock().Object, + ContainerMocker.GetMock().Object, + Mocker.GetMock().Object); + } +} diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs new file mode 100644 index 0000000..87bf106 --- /dev/null +++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs @@ -0,0 +1,603 @@ +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; + +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Options; + +using static FrostFS.Session.SessionToken.Types.Body; + +namespace FrostFS.SDK.SmokeTests; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class PoolSmokeTests : SmokeTestsBase +{ + private static readonly PrmWait lightWait = new(100, 1); + + private InitParameters GetDefaultParams() + { + return new InitParameters + { + Key = keyString.LoadWif(), + + NodeParams = [new(1, this.url, 100.0f)], + DialOptions = [new() + { + Authority = "", + Block = false, + DisableHealthCheck = false, + DisableRetry = false, + ReturnLastError = true, + Timeout = 30_000_000 + } + ], + ClientBuilder = null, + GracefulCloseOnSwitchTimeout = 30_000_000, + Logger = null + }; + } + + [Fact] + public async void NetworkMapTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()); + + Assert.Null(error); + + var result = await pool.GetNetmapSnapshotAsync(default); + + Assert.True(result.Epoch > 0); + Assert.Single(result.NodeInfoCollection); + + var item = result.NodeInfoCollection[0]; + Assert.Equal(2, item.Version.Major); + Assert.Equal(13, item.Version.Minor); + Assert.Equal(NodeState.Online, item.State); + Assert.True(item.PublicKey.Length > 0); + Assert.Single(item.Addresses); + Assert.Equal(9, item.Attributes.Count); + } + + [Fact] + public async void NodeInfoTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()); + + Assert.Null(error); + + var result = await pool.GetNodeInfoAsync(); + + 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); + } + + [Fact] + public async void NodeInfoStatisticsTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var callbackText = string.Empty; + + var ctx = new CallContext + { + Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" + }; + + var error = await pool.Dial(ctx).ConfigureAwait(true); + + Assert.Null(error); + + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + var result = await client.GetNodeInfoAsync(); + + Assert.False(string.IsNullOrEmpty(callbackText)); + Assert.Contains(" took ", callbackText, StringComparison.Ordinal); + } + + [Fact] + public async void GetSessionTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + var prm = new PrmSessionCreate(100); + + var token = await pool.CreateSessionAsync(prm).ConfigureAwait(true); + + var session = new Session.SessionToken().Deserialize(token.Token); + + var ownerHash = Base58.Decode(OwnerId!.Value); + + Assert.NotNull(session); + Assert.Null(session.Body.Container); + Assert.Null(session.Body.Object); + Assert.Equal(16, session.Body.Id.Length); + Assert.Equal(100ul, session.Body.Lifetime.Exp); + Assert.Equal(ownerHash, session.Body.OwnerId.Value); + Assert.Equal(33, session.Body.SessionKey.Length); + Assert.Equal(ContextOneofCase.None, session.Body.ContextCase); + } + + [Fact] + public async void CreateObjectWithSessionToken() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); + + createContainerParam.XHeaders.Add("key1", "value1"); + + var containerId = await pool.CreateContainerAsync(createContainerParam); + + var bytes = GetRandomBytes(1024); + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = false, + SessionToken = token + }; + + var objectId = await pool.PutObjectAsync(param).ConfigureAwait(true); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + } + + [Fact] + public async void FilterTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + { + WaitParams = lightWait + }; + + var containerId = await pool.CreateContainerAsync(createContainerParam); + + var bytes = new byte[] { 1, 2, 3 }; + + var ParentHeader = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular) + { + PayloadLength = 3 + }; + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")], + new FrostFsSplit()), + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var objectId = await pool.PutObjectAsync(param); + + var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + + var ecdsaKey = this.keyString.LoadWif(); + + var networkInfo = await pool.GetNetmapSnapshotAsync(); + + await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); + + await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); + + await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId)); + + await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); + + await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); + + await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + + await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); + + await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); + + var checkSum = CheckSum.CreateCheckSum(bytes); + + await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); + + await CheckFilter(pool, containerId, new FilterByPhysicallyStored()); + } + + private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter) + { + var resultObjectsCount = 0; + + PrmObjectSearch searchParam = new(containerId) { Filters = [filter] }; + + await foreach (var objId in pool.SearchObjectsAsync(searchParam)) + { + resultObjectsCount++; + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId)); + } + + Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioTest(int objectSize) + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + bool callbackInvoked = false; + var ctx = new CallContext + { + // Timeout = TimeSpan.FromSeconds(20), + Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }) + }; + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])) + { + Context = ctx + }; + + var createdContainer = await pool.CreateContainerAsync(createContainerParam); + + var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx }); + Assert.NotNull(container); + Assert.True(callbackInvoked); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = false, + Context = new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + } + }; + + var objectId = await pool.PutObjectAsync(param); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + { + hasObject = true; + + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes!); + Assert.Equal("fileName", objHeader.Attributes!.First().Key); + Assert.Equal("test", objHeader.Attributes!.First().Value); + } + + Assert.True(hasObject); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + + await foreach (var _ in pool.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioWithSessionTest(int objectSize) + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + + await Cleanup(pool); + + var ctx = new CallContext + { + Timeout = TimeSpan.FromSeconds(20), + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + { + Context = ctx + }; + + var container = await pool.CreateContainerAsync(createContainerParam); + + var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx }); + Assert.NotNull(containerInfo); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: container, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = false, + Context = new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }, + SessionToken = token + }; + + var objectId = await pool.PutObjectAsync(param); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) + { + hasObject = true; + + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token }); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token }); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + + await foreach (var _ in pool.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB + [InlineData(64 * 1024 * 1024 - 1)] + [InlineData(64 * 1024 * 1024 + 1)] + [InlineData(2 * 64 * 1024 * 1024 + 256)] + [InlineData(200)] + public async void ClientCutScenarioTest(int objectSize) + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) + { + WaitParams = lightWait + }; + + var containerId = await pool.CreateContainerAsync(createContainerParam); + + var ctx = new CallContext + { + Timeout = TimeSpan.FromSeconds(10), + Interceptors = new([new MetricsInterceptor()]) + }; + + var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); + + Assert.NotNull(container); + + byte[] bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = true + }; + + var objectId = await pool.PutObjectAsync(param); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) + { + hasObject = true; + + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + 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 @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await CheckFilter(pool, containerId, new FilterByRootObject()); + + await Cleanup(pool); + + var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); + + IAsyncEnumerator? enumerator = null; + do + { + if (deadline <= DateTime.UtcNow) + { + Assert.Fail("Containers exist"); + break; + } + + enumerator = pool.ListContainersAsync().GetAsyncEnumerator(); + await Task.Delay(500); + } + while (await enumerator!.MoveNextAsync()); + } + + private static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + + private static IOptions GetSingleOwnerOptions(string key, string url) + { + return Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = url + }); + } + + private static IOptions GetOptions(string url) + { + return Options.Create(new ClientSettings + { + Host = url + }); + } + + static async Task Cleanup(Pool pool) + { + await foreach (var cid in pool.ListContainersAsync()) + { + await pool.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }).ConfigureAwait(true); + } + } +} diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index 2e17816..e343c82 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -1,56 +1,11 @@ -using System.Security.Cryptography; +using System.Diagnostics.CodeAnalysis; using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.Cryptography; - -using Microsoft.Extensions.Options; namespace FrostFS.SDK.Tests; -public abstract class SessionTestsBase -{ - protected readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - - protected IOptions Settings { get; set; } - - - protected ECDsa ECDsaKey { get; set; } - protected FrostFsOwner OwnerId { get; set; } - protected SessionMocker Mocker { get; set; } - - protected SessionTestsBase() - { - Settings = Options.Create(new SingleOwnerClientSettings - { - Key = key, - Host = "http://localhost:8080" - }); - - ECDsaKey = key.LoadWif(); - OwnerId = FrostFsOwner.FromKey(ECDsaKey); - - Mocker = new SessionMocker(this.key) - { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), - Version = new FrostFsVersion(2, 13) - }; - } - - protected IFrostFSClient GetClient() - { - return ClientV2.FrostFSClient.GetTestInstance( - Settings, - null, - new NetworkMocker(this.key).GetMock().Object, - Mocker.GetMock().Object, - new ContainerMocker(this.key).GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); - } -} - +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class SessionTest : SessionTestsBase { [Theory] @@ -64,7 +19,7 @@ public class SessionTest : SessionTestsBase if (useContext) { param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new Context + param.Context = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -101,7 +56,6 @@ public class SessionTest : SessionTestsBase Assert.NotNull(Mocker.CreateSessionRequest.MetaHeader); Assert.Equal(Mocker.Version.ToMessage(), Mocker.CreateSessionRequest.MetaHeader.Version); - Assert.Null(Mocker.Metadata); if (useContext) diff --git a/src/FrostFS.SDK.Tests/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/SessionTestsBase.cs new file mode 100644 index 0000000..a8fbdaf --- /dev/null +++ b/src/FrostFS.SDK.Tests/SessionTestsBase.cs @@ -0,0 +1,48 @@ +using System.Security.Cryptography; + +using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests; + +public abstract class SessionTestsBase +{ + internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + protected IOptions Settings { get; set; } + + protected ECDsa ECDsaKey { get; set; } + protected FrostFsOwner OwnerId { get; set; } + protected SessionMocker Mocker { get; set; } + + protected SessionTestsBase() + { + Settings = Options.Create(new SingleOwnerClientSettings + { + Key = key, + Host = "http://localhost:8080" + }); + + ECDsaKey = key.LoadWif(); + OwnerId = FrostFsOwner.FromKey(ECDsaKey); + + Mocker = new SessionMocker(this.key) + { + PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + Version = new FrostFsVersion(2, 13) + }; + } + + protected IFrostFSClient GetClient() + { + return ClientV2.FrostFSClient.GetTestInstance( + Settings, + null, + new NetworkMocker(this.key).GetMock().Object, + Mocker.GetMock().Object, + new ContainerMocker(this.key).GetMock().Object, + new ObjectMocker(this.key).GetMock().Object); + } +} diff --git a/src/FrostFS.SDK.Tests/SmokeTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs similarity index 88% rename from src/FrostFS.SDK.Tests/SmokeTests.cs rename to src/FrostFS.SDK.Tests/SmokeClientTests.cs index 9c86ff9..54868d6 100644 --- a/src/FrostFS.SDK.Tests/SmokeTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs @@ -1,24 +1,42 @@ +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; + using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.Cryptography; + using Microsoft.Extensions.Options; using static FrostFS.Session.SessionToken.Types.Body; -using FrostFS.SDK.ClientV2; namespace FrostFS.SDK.SmokeTests; -public class SmokeTests : SmokeTestsBase +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class SmokeClientTests : SmokeTestsBase { private static readonly PrmWait lightWait = new(100, 1); + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void AccountTest(bool isSingleOnwerClient) + { + using var client = isSingleOnwerClient + ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) + : FrostFSClient.GetInstance(GetOptions(this.url)); + + PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; + + var result = await client.GetBalanceAsync(prm); + } + [Theory] [InlineData(false)] [InlineData(true)] public async void NetworkMapTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; var result = await client.GetNetmapSnapshotAsync(prm); @@ -41,7 +59,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(true)] public async void NodeInfoTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; @@ -56,14 +74,14 @@ public class SmokeTests : SmokeTestsBase } [Fact] - public async void NodeInfo_Statistics_Test() + public async void NodeInfoStatisticsTest() { - var ctx = new Context + var ctx = new CallContext { Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") }; - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); var result = await client.GetNodeInfoAsync(); } @@ -73,7 +91,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(true)] public async void GetSessionTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx }; @@ -81,7 +99,7 @@ public class SmokeTests : SmokeTestsBase var session = new Session.SessionToken().Deserialize(token.Token); - var ownerHash = Base58.Decode(OwnerId.Value); + var ownerHash = Base58.Decode(OwnerId!.Value); Assert.NotNull(session); Assert.Null(session.Body.Container); @@ -96,7 +114,7 @@ public class SmokeTests : SmokeTestsBase [Fact] public async void CreateObjectWithSessionToken() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); await Cleanup(client); @@ -144,11 +162,7 @@ public class SmokeTests : SmokeTestsBase [Fact] public async void FilterTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); - - //var prm = new PrmApeChainList(new FrostFsChainTarget(FrostFsTargetType.Namespace, "root")); - - //var chains = await client.ListChainAsync(prm); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); await Cleanup(client); @@ -184,7 +198,7 @@ public class SmokeTests : SmokeTestsBase var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); - var ecdsaKey = this.key.LoadWif(); + var ecdsaKey = this.keyString.LoadWif(); var networkInfo = await client.GetNetmapSnapshotAsync(); @@ -192,7 +206,7 @@ public class SmokeTests : SmokeTestsBase await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split.SplitId)); + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId)); await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); @@ -232,12 +246,12 @@ public class SmokeTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); await Cleanup(client); bool callbackInvoked = false; - var ctx = new Context + var ctx = new CallContext { // Timeout = TimeSpan.FromSeconds(20), Callback = new((CallStatistics cs) => @@ -269,7 +283,7 @@ public class SmokeTests : SmokeTestsBase [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, - Context = new Context + Context = new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) } @@ -286,6 +300,7 @@ public class SmokeTests : SmokeTestsBase var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); @@ -304,7 +319,7 @@ public class SmokeTests : SmokeTestsBase ms.Write(chunk.Value.Span); } - Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); @@ -320,13 +335,13 @@ public class SmokeTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); await Cleanup(client); - var ctx = new Context + var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(20), Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) @@ -353,7 +368,7 @@ public class SmokeTests : SmokeTestsBase [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, - Context = new Context + Context = new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) }, @@ -371,6 +386,7 @@ public class SmokeTests : SmokeTestsBase var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token }); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); Assert.Equal("fileName", objHeader.Attributes.First().Key); Assert.Equal("test", objHeader.Attributes.First().Value); @@ -389,7 +405,7 @@ public class SmokeTests : SmokeTestsBase ms.Write(chunk.Value.Span); } - Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); @@ -408,7 +424,7 @@ public class SmokeTests : SmokeTestsBase [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.key, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); await Cleanup(client); @@ -419,7 +435,7 @@ public class SmokeTests : SmokeTestsBase var containerId = await client.CreateContainerAsync(createContainerParam); - var ctx = new Context + var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(10), Interceptors = new([new MetricsInterceptor()]) @@ -452,6 +468,7 @@ public class SmokeTests : SmokeTestsBase var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); 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); @@ -470,7 +487,7 @@ public class SmokeTests : SmokeTestsBase ms.Write(chunk.Value.Span); } - Assert.Equal(MD5.HashData(bytes), MD5.HashData(downloadedBytes)); + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await CheckFilter(client, containerId, new FilterByRootObject()); diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index 5bf7733..6b42144 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -7,24 +7,24 @@ namespace FrostFS.SDK.SmokeTests; public abstract class SmokeTestsBase { - protected readonly string key = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; - - protected readonly string url = "http://172.23.32.4:8080"; + internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; - protected ECDsa Key { get; } + internal readonly string url = "http://172.23.32.4:8080"; - protected FrostFsOwner OwnerId { get; } + protected ECDsa? Key { get; } - protected FrostFsVersion Version { get; } + protected FrostFsOwner? OwnerId { get; } - protected Context Ctx { get; } + protected FrostFsVersion? Version { get; } + + protected CallContext? Ctx { get; } protected SmokeTestsBase() { - Key = key.LoadWif(); + Key = keyString.LoadWif(); OwnerId = FrostFsOwner.FromKey(Key); Version = new FrostFsVersion(2, 13); - Ctx = new Context { Key = Key, OwnerId = OwnerId, Version = Version }; + Ctx = new CallContext { Key = Key, OwnerId = OwnerId, Version = Version }; } } From ee2079837906b274ccaf075a42cf5b55c5d1942b Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 1 Nov 2024 10:30:28 +0300 Subject: [PATCH 25/65] [#24] Client: Implement pool part2 Signed-off-by: Pavel Gross --- .../FrostFsInvalidObjectException.cs | 18 + .../Exceptions/FrostFsResponseException.cs | 25 ++ .../Exceptions/FrostFsStreamException.cs | 18 + .../Exceptions/InvalidObjectException.cs | 18 - .../Exceptions/ResponseException.cs | 25 -- .../FrostFS.SDK.ClientV2.csproj | 4 +- src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 81 ++--- .../Interceptors/ErrorInterceptor.cs | 68 ++++ .../Interceptors/MetricsInterceptor.cs | 14 +- .../Interfaces/IFrostFSClient.cs | 2 - .../Logging/FrostFsMessages.cs | 24 ++ .../Models/Client/ClientSettings.cs | 6 +- .../Models/Containers/FrostFsContainerId.cs | 4 +- .../Models/Containers/FrostFsContainerInfo.cs | 2 +- .../Models/Object/FrostFsObject.cs | 4 +- .../Parameters/CallContext.cs | 10 +- .../Parameters/IContext.cs | 2 +- .../Parameters/PrmApeChainList.cs | 2 +- .../Parameters/PrmApeChainRemove.cs | 2 +- .../Parameters/PrmApeRemoveAdd.cs | 2 +- .../Parameters/PrmBalance.cs | 2 +- .../Parameters/PrmBase.cs | 4 +- .../Parameters/PrmContainerCreate.cs | 2 +- .../Parameters/PrmContainerDelete.cs | 2 +- .../Parameters/PrmContainerGet.cs | 2 +- .../Parameters/PrmContainerGetAll.cs | 2 +- .../Parameters/PrmNetmapSnapshot.cs | 2 +- .../Parameters/PrmNetworkSettings.cs | 2 +- .../Parameters/PrmNodeInfo.cs | 2 +- .../Parameters/PrmObjectDelete.cs | 2 +- .../Parameters/PrmObjectGet.cs | 2 +- .../Parameters/PrmObjectHeadGet.cs | 2 +- .../Parameters/PrmObjectPut.cs | 2 +- .../Parameters/PrmObjectSearch.cs | 2 +- .../Parameters/PrmSessionCreate.cs | 2 +- .../Parameters/PrmSingleObjectPut.cs | 2 +- .../Poll/ClientStatusMonitor.cs | 20 +- .../Poll/ClientWrapper.cs | 72 ++-- src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs | 16 - .../Poll/InitParameters.cs | 4 +- src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs | 4 +- src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 338 ++++++++++++------ .../Poll/RebalanceParameters.cs | 2 +- src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs | 9 +- .../Services/AccountingServiceProvider.cs | 2 +- .../Services/ApeManagerServiceProvider.cs | 15 +- .../Services/ContainerServiceProvider.cs | 36 +- .../Services/NetmapServiceProvider.cs | 25 +- .../Services/ObjectServiceProvider.cs | 52 +-- .../Services/SessionServiceProvider.cs | 4 +- .../Services/Shared/ContextAccessor.cs | 4 +- .../Services/Shared/SessionProvider.cs | 5 +- ...EnvironmentContext.cs => ClientContext.cs} | 22 +- .../Tools/ObjectReader.cs | 6 +- src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs | 2 +- src/FrostFS.SDK.ClientV2/Tools/Verifier.cs | 4 +- src/FrostFS.SDK.Tests/CallbackInterceptor.cs | 33 ++ src/FrostFS.SDK.Tests/MetricsInterceptor.cs | 41 --- src/FrostFS.SDK.Tests/NetworkTest.cs | 37 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 6 +- src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 103 +++--- src/FrostFS.SDK.Tests/SessionTests.cs | 13 +- src/FrostFS.SDK.Tests/SmokeClientTests.cs | 87 +++-- 63 files changed, 801 insertions(+), 526 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs create mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs delete mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs delete mode 100644 src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs create mode 100644 src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs create mode 100644 src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs delete mode 100644 src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs rename src/FrostFS.SDK.ClientV2/Tools/{EnvironmentContext.cs => ClientContext.cs} (62%) create mode 100644 src/FrostFS.SDK.Tests/CallbackInterceptor.cs delete mode 100644 src/FrostFS.SDK.Tests/MetricsInterceptor.cs diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs new file mode 100644 index 0000000..87e20e7 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class FrostFsInvalidObjectException : FrostFsException +{ + public FrostFsInvalidObjectException() + { + } + + public FrostFsInvalidObjectException(string message) : base(message) + { + } + + public FrostFsInvalidObjectException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs new file mode 100644 index 0000000..6ef30aa --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs @@ -0,0 +1,25 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class FrostFsResponseException : FrostFsException +{ + public FrostFsResponseStatus? Status { get; private set; } + + public FrostFsResponseException() + { + } + + public FrostFsResponseException(FrostFsResponseStatus status) + { + Status = status; + } + + public FrostFsResponseException(string message) : base(message) + { + } + + public FrostFsResponseException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs new file mode 100644 index 0000000..45472e4 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs @@ -0,0 +1,18 @@ +using System; + +namespace FrostFS.SDK.ClientV2; + +public class FrostFsStreamException : FrostFsException +{ + public FrostFsStreamException() + { + } + + public FrostFsStreamException(string message) : base(message) + { + } + + public FrostFsStreamException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs deleted file mode 100644 index c15e591..0000000 --- a/src/FrostFS.SDK.ClientV2/Exceptions/InvalidObjectException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace FrostFS.SDK.ClientV2; - -public class InvalidObjectException : Exception -{ - public InvalidObjectException() - { - } - - public InvalidObjectException(string message) : base(message) - { - } - - public InvalidObjectException(string message, Exception innerException) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs b/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs deleted file mode 100644 index ce7f19a..0000000 --- a/src/FrostFS.SDK.ClientV2/Exceptions/ResponseException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace FrostFS.SDK.ClientV2; - -public class ResponseException : Exception -{ - public FrostFsResponseStatus? Status { get; private set; } - - public ResponseException() - { - } - - public ResponseException(FrostFsResponseStatus status) - { - Status = status; - } - - public ResponseException(string message) : base(message) - { - } - - public ResponseException(string message, Exception innerException) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj index b16e7e7..fefaaf2 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj @@ -22,10 +22,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs index 2ca9e96..e13b6b6 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs @@ -39,7 +39,7 @@ public class FrostFSClient : IFrostFSClient internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; } - internal EnvironmentContext ClientCtx { get; set; } + internal ClientContext ClientCtx { get; set; } public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { @@ -93,7 +93,7 @@ public class FrostFSClient : IFrostFSClient var ecdsaKey = settings.Value.Key.LoadWif(); FrostFsOwner.FromKey(ecdsaKey); - ClientCtx = new EnvironmentContext( + ClientCtx = new ClientContext( client: this, key: ecdsaKey, owner: FrostFsOwner.FromKey(ecdsaKey), @@ -108,13 +108,13 @@ public class FrostFSClient : IFrostFSClient private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) { - var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); + var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized"); clientSettings.Validate(); var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - ClientCtx = new EnvironmentContext( + ClientCtx = new ClientContext( this, key: null, owner: null, @@ -127,7 +127,7 @@ public class FrostFSClient : IFrostFSClient private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) { - var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized"); + var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized"); clientSettings.Validate(); @@ -135,7 +135,7 @@ public class FrostFSClient : IFrostFSClient var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - ClientCtx = new EnvironmentContext( + ClientCtx = new ClientContext( this, key: ecdsaKey, owner: FrostFsOwner.FromKey(ecdsaKey), @@ -146,14 +146,17 @@ public class FrostFSClient : IFrostFSClient // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); } - internal FrostFSClient(WrapperPrm prm) + internal FrostFSClient(WrapperPrm prm, SessionCache cache) { - ClientCtx = new EnvironmentContext( + ClientCtx = new ClientContext( client: this, key: prm.Key, owner: FrostFsOwner.FromKey(prm.Key!), channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions), - version: new FrostFsVersion(2, 13)); + version: new FrostFsVersion(2, 13)) + { + SessionCache = cache + }; } public void Dispose() @@ -363,10 +366,10 @@ public class FrostFSClient : IFrostFSClient private async void CheckFrostFsVersionSupport(CallContext? ctx = default) { - var args = new PrmNodeInfo { Context = ctx }; + var args = new PrmNodeInfo(ctx); if (ctx?.Version == null) - throw new InvalidObjectException(nameof(ctx.Version)); + throw new ArgumentNullException(nameof(ctx), "Version must be initialized"); var service = GetNetmapService(args); var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false); @@ -378,18 +381,16 @@ public class FrostFSClient : IFrostFSClient } } - private CallInvoker? SetupEnvironment(IContext ctx) + private CallInvoker? SetupClientContext(IContext ctx) { if (isDisposed) - throw new InvalidObjectException("Client is disposed."); + throw new FrostFsInvalidObjectException("Client is disposed."); - ctx.Context ??= new CallContext(); - - if (ctx.Context.Key == null) + if (ctx.Context!.Key == null) { if (ClientCtx.Key == null) { - throw new InvalidObjectException("Key is not initialized."); + throw new ArgumentNullException(nameof(ctx), "Key is not initialized."); } ctx.Context.Key = ClientCtx.Key.ECDsaKey; @@ -404,24 +405,23 @@ public class FrostFSClient : IFrostFSClient { if (ClientCtx.Version == null) { - throw new InvalidObjectException("Version is not initialized."); + throw new ArgumentNullException(nameof(ctx), "Version is not initialized."); } ctx.Context.Version = ClientCtx.Version; } CallInvoker? callInvoker = null; - if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0) - { - foreach (var interceptor in ctx.Context.Interceptors) - { - callInvoker = AddInvoker(callInvoker, interceptor); - } - } + + foreach (var interceptor in ctx.Context.Interceptors) + callInvoker = AddInvoker(callInvoker, interceptor); if (ctx.Context.Callback != null) callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback)); + if (ctx.Context.PoolErrorHandler != null) + callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler)); + return callInvoker; CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor) @@ -429,7 +429,7 @@ public class FrostFSClient : IFrostFSClient if (callInvoker == null) callInvoker = ClientCtx.Channel.Intercept(interceptor); else - callInvoker.Intercept(interceptor); + callInvoker = callInvoker.Intercept(interceptor); return callInvoker; } @@ -437,7 +437,7 @@ public class FrostFSClient : IFrostFSClient private NetmapServiceProvider GetNetmapService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = NetmapServiceClient ?? (callInvoker != null ? new NetmapService.NetmapServiceClient(callInvoker) : new NetmapService.NetmapServiceClient(ClientCtx.Channel)); @@ -447,7 +447,7 @@ public class FrostFSClient : IFrostFSClient private SessionServiceProvider GetSessionService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = SessionServiceClient ?? (callInvoker != null ? new SessionService.SessionServiceClient(callInvoker) : new SessionService.SessionServiceClient(ClientCtx.Channel)); @@ -457,7 +457,7 @@ public class FrostFSClient : IFrostFSClient private ApeManagerServiceProvider GetApeManagerService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = ApeManagerServiceClient ?? (callInvoker != null ? new APEManagerService.APEManagerServiceClient(callInvoker) : new APEManagerService.APEManagerServiceClient(ClientCtx.Channel)); @@ -467,7 +467,7 @@ public class FrostFSClient : IFrostFSClient private AccountingServiceProvider GetAccouningService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = AccountingServiceClient ?? (callInvoker != null ? new AccountingService.AccountingServiceClient(callInvoker) : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); @@ -477,7 +477,7 @@ public class FrostFSClient : IFrostFSClient private ContainerServiceProvider GetContainerService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = ContainerServiceClient ?? (callInvoker != null ? new ContainerService.ContainerServiceClient(callInvoker) : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); @@ -487,7 +487,7 @@ public class FrostFSClient : IFrostFSClient private ObjectServiceProvider GetObjectService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = ObjectServiceClient ?? (callInvoker != null ? new ObjectService.ObjectServiceClient(callInvoker) : new ObjectService.ObjectServiceClient(ClientCtx.Channel)); @@ -497,7 +497,7 @@ public class FrostFSClient : IFrostFSClient private AccountingServiceProvider GetAccountService(IContext ctx) { - var callInvoker = SetupEnvironment(ctx); + var callInvoker = SetupClientContext(ctx); var client = AccountingServiceClient ?? (callInvoker != null ? new AccountingService.AccountingServiceClient(callInvoker) : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); @@ -527,19 +527,12 @@ public class FrostFSClient : IFrostFSClient public async Task Dial(CallContext ctx) { - try - { - var prm = new PrmBalance { Context = ctx }; + var prm = new PrmBalance(ctx); - var service = GetAccouningService(prm); - var balance = await service.GetBallance(prm).ConfigureAwait(false); + var service = GetAccouningService(prm); + _ = await service.GetBallance(prm).ConfigureAwait(false); - return null; - } - catch (FrostFsException ex) - { - return ex.Message; - } + return null; } public bool RestartIfUnhealthy(CallContext ctx) diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs new file mode 100644 index 0000000..d76477f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; + +using Grpc.Core; +using Grpc.Core.Interceptors; + +namespace FrostFS.SDK.ClientV2; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", + Justification = "parameters are provided by GRPC infrastructure")] +public class ErrorInterceptor(Action handler) : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + public override AsyncClientStreamingCall AsyncClientStreamingCall( + ClientInterceptorContext context, + AsyncClientStreamingCallContinuation continuation) + { + var call = continuation(context); + + return new AsyncClientStreamingCall( + call.RequestStream, + HandleStreamResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + try + { + return await call; + } + catch (Exception ex) + { + handler(ex); + throw; + } + } + + private async Task HandleStreamResponse(AsyncClientStreamingCall call) + { + try + { + return await call; + } + catch (Exception ex) + { + handler(ex); + throw; + } + } +} diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs index 9b3199c..f4d93c0 100644 --- a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs @@ -7,6 +7,8 @@ using Grpc.Core.Interceptors; namespace FrostFS.SDK.ClientV2; +[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", + Justification = "parameters are provided by GRPC infrastructure")] public class MetricsInterceptor(Action callback) : Interceptor { public override AsyncUnaryCall AsyncUnaryCall( @@ -14,11 +16,6 @@ public class MetricsInterceptor(Action callback) : Interceptor ClientInterceptorContext context, AsyncUnaryCallContinuation continuation) { - if (continuation is null) - { - throw new ArgumentNullException(nameof(continuation)); - } - var call = continuation(request, context); return new AsyncUnaryCall( @@ -33,9 +30,6 @@ public class MetricsInterceptor(Action callback) : Interceptor ClientInterceptorContext context, AsyncClientStreamingCallContinuation continuation) { - if (continuation is null) - throw new ArgumentNullException(nameof(continuation)); - var call = continuation(context); return new AsyncClientStreamingCall( @@ -52,7 +46,7 @@ public class MetricsInterceptor(Action callback) : Interceptor var watch = new Stopwatch(); watch.Start(); - var response = await call.ResponseAsync.ConfigureAwait(false); + var response = await call; watch.Stop(); @@ -68,7 +62,7 @@ public class MetricsInterceptor(Action callback) : Interceptor var watch = new Stopwatch(); watch.Start(); - var response = await call.ResponseAsync.ConfigureAwait(false); + var response = await call; watch.Stop(); diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 193d128..e81343b 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -61,7 +61,5 @@ public interface IFrostFSClient : IDisposable public Task Dial(CallContext ctx); - public bool RestartIfUnhealthy(CallContext ctx); - public void Close(); } diff --git a/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs b/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs new file mode 100644 index 0000000..2fd1fe1 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Logging; + +namespace FrostFS.SDK.ClientV2; + +internal static partial class FrostFsMessages +{ + [LoggerMessage(100, + LogLevel.Warning, + "Failed to create frostfs session token for client. Address {address}, {error}", + EventName = nameof(SessionCreationError))] + internal static partial void SessionCreationError(ILogger logger, string address, string error); + + [LoggerMessage(101, + LogLevel.Warning, + "Error threshold reached. Address {address}, threshold {threshold}", + EventName = nameof(ErrorЕhresholdReached))] + internal static partial void ErrorЕhresholdReached(ILogger logger, string address, uint threshold); + + [LoggerMessage(102, + LogLevel.Warning, + "Health has changed: {address} healthy {healthy}, reason {error}", + EventName = nameof(HealthChanged))] + internal static partial void HealthChanged(ILogger logger, string address, bool healthy, string error); +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs index b1faf79..6ebec1f 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs @@ -15,7 +15,7 @@ public class ClientSettings { var errors = CheckFields(); if (errors != null) - ThrowException(errors); + ThrowSettingsException(errors); } protected Collection? CheckFields() @@ -29,7 +29,7 @@ public class ClientSettings return null; } - protected static void ThrowException(Collection errors) + protected static void ThrowSettingsException(Collection errors) { if (errors is null) { @@ -55,7 +55,7 @@ public class SingleOwnerClientSettings : ClientSettings { var errors = CheckFields(); if (errors != null) - ThrowException(errors); + ThrowSettingsException(errors); } protected new Collection? CheckFields() diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs index 1ebbe04..06b87ca 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs @@ -31,7 +31,7 @@ public class FrostFsContainerId return this.modelId; } - throw new InvalidObjectException(); + throw new FrostFsInvalidObjectException(); } internal ContainerID ContainerID @@ -47,7 +47,7 @@ public class FrostFsContainerId return this.containerID; } - throw new InvalidObjectException(); + throw new FrostFsInvalidObjectException(); } } diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs index e7336b2..affdbee 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs @@ -88,7 +88,7 @@ public class FrostFsContainerInfo { if (PlacementPolicy == null) { - throw new InvalidObjectException("PlacementPolicy is null"); + throw new ArgumentNullException("PlacementPolicy is null"); } this.container = new Container.Container() diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs index a6c82db..f366b9e 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ClientV2; +using System; namespace FrostFS.SDK; @@ -67,7 +67,7 @@ public class FrostFsObject public void SetParent(FrostFsObjectHeader largeObjectHeader) { if (Header?.Split == null) - throw new InvalidObjectException("The object is not initialized properly"); + throw new ArgumentNullException(nameof(largeObjectHeader), "Split value must not be null"); Header.Split.ParentHeader = largeObjectHeader; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs index ff4bc27..24f076e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs @@ -13,10 +13,10 @@ namespace FrostFS.SDK.ClientV2; public class CallContext() { - private ReadOnlyCollection? interceptors; - private ByteString? publicKeyCache; + internal Action? PoolErrorHandler { get; set; } + public ECDsa? Key { get; set; } public FrostFsOwner? OwnerId { get; set; } @@ -31,11 +31,7 @@ public class CallContext() public Action? Callback { get; set; } - public ReadOnlyCollection? Interceptors - { - get { return this.interceptors; } - set { this.interceptors = value; } - } + public Collection Interceptors { get; } = []; public ByteString? GetPublicKeyCache() { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs index 8c6f1df..128c6a0 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs @@ -7,5 +7,5 @@ public interface IContext /// callbacks, interceptors. /// /// Additional parameters for calling the method - CallContext? Context { get; set; } + CallContext? Context { get; } } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs index a68e788..4202685 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmApeChainList(FrostFsChainTarget target) : PrmBase +public sealed class PrmApeChainList(FrostFsChainTarget target, CallContext? ctx = null) : PrmBase(ctx) { public FrostFsChainTarget Target { get; } = target; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs index 9371b43..a7a9f5a 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain) : PrmBase +public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) { public FrostFsChainTarget Target { get; } = target; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs index 330f601..75ac0e7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain) : PrmBase +public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) { public FrostFsChainTarget Target { get; } = target; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs index 3a07fde..df36980 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmBalance() : PrmBase +public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx) { } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs index 79e5e7e..e6fb030 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2; -public class PrmBase(NameValueCollection? xheaders = null) : IContext +public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext { /// /// FrostFS request X-Headers @@ -10,5 +10,5 @@ public class PrmBase(NameValueCollection? xheaders = null) : IContext public NameValueCollection XHeaders { get; } = xheaders ?? []; /// - public CallContext? Context { get; set; } + public CallContext Context { get; } = ctx ?? new CallContext(); } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs index da4c50b..68eadc9 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmContainerCreate(FrostFsContainerInfo container) : PrmBase, ISessionToken +public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsContainerInfo Container { get; set; } = container; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs index c97e80f..9de85f8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmContainerDelete(FrostFsContainerId containerId) : PrmBase, ISessionToken +public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs index f0c734b..85f0821 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmContainerGet(FrostFsContainerId container) : PrmBase +public sealed class PrmContainerGet(FrostFsContainerId container, CallContext? ctx = null) : PrmBase(ctx) { public FrostFsContainerId Container { get; set; } = container; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs index b7d3980..ceec0c1 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmContainerGetAll() : PrmBase() +public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx) { } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs index 6031ca6..0938f86 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmNetmapSnapshot() : PrmBase +public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx) { } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs index f014f14..de2c13d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmNetworkSettings() : PrmBase +public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx) { } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs index 05b541d..dbbcdd8 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs @@ -1,5 +1,5 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmNodeInfo() : PrmBase +public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx) { } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs index f1dad65..e168b31 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs index 81a1e2f..11e64d7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs index 40410f3..0b948b7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId) : PrmBase, ISessionToken +public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsContainerId ContainerId { get; set; } = containerId; diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs index 3d216d5..47a1e1e 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs @@ -2,7 +2,7 @@ using System.IO; namespace FrostFS.SDK.ClientV2; -public sealed class PrmObjectPut : PrmBase, ISessionToken +public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken { /// /// Need to provide values like ContainerId and ObjectType to create and object. diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs index a9310dd..723c1e7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmObjectSearch(FrostFsContainerId containerId, params IObjectFilter[] filters) : PrmBase, ISessionToken +public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken { /// /// Defines container for the search diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs index d7bcdb6..ab02f69 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmSessionCreate(ulong expiration) : PrmBase +public sealed class PrmSessionCreate(ulong expiration, CallContext? ctx = null) : PrmBase(ctx) { public ulong Expiration { get; set; } = expiration; } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs index 3a8fa5d..8cb8c19 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject) : PrmBase, ISessionToken +public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { public FrostFsObject FrostFsObject { get; set; } = frostFsObject; diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs index e864148..7200b37 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs @@ -27,8 +27,7 @@ public class ClientStatusMonitor : IClientStatus MethodIndex.methodSessionCreate, MethodIndex.methodAPEManagerAddChain, MethodIndex.methodAPEManagerRemoveChain, - MethodIndex.methodAPEManagerListChains, - MethodIndex.methodLast + MethodIndex.methodAPEManagerListChains ]; public static string GetMethodName(MethodIndex index) @@ -53,7 +52,7 @@ public class ClientStatusMonitor : IClientStatus MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain", MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain", MethodIndex.methodAPEManagerListChains => "APEManagerListChains", - _ => throw new NotImplementedException(), + _ => throw new ArgumentException("Unknown method", nameof(index)), }; } @@ -62,13 +61,12 @@ public class ClientStatusMonitor : IClientStatus private readonly ILogger? logger; private int healthy; - public ClientStatusMonitor(ILogger? logger, string address, uint errorThreshold) + public ClientStatusMonitor(ILogger? logger, string address) { this.logger = logger; healthy = (int)HealthyStatus.Healthy; - Address = address; - ErrorThreshold = errorThreshold; + Address = address; Methods = new MethodStatus[MethodIndexes.Length]; for (int i = 0; i < MethodIndexes.Length; i++) @@ -79,7 +77,7 @@ public class ClientStatusMonitor : IClientStatus public string Address { get; } - internal uint ErrorThreshold { get; } + internal uint ErrorThreshold { get; set; } public uint CurrentErrorCount { get; set; } @@ -89,7 +87,8 @@ public class ClientStatusMonitor : IClientStatus public bool IsHealthy() { - return Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy; + var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy; + return res; } public bool IsDialed() @@ -124,14 +123,13 @@ public class ClientStatusMonitor : IClientStatus if (thresholdReached) { SetUnhealthy(); - CurrentErrorCount = 0; } } - if (thresholdReached) + if (thresholdReached && logger != null) { - logger?.Log(LogLevel.Warning, "Error threshold reached. Address {Address}, threshold {Threshold}", Address, ErrorThreshold); + FrostFsMessages.ErrorЕhresholdReached(logger, Address, ErrorThreshold); } } diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs index 3891250..ddde002 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs @@ -1,38 +1,36 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; + +using Grpc.Core; namespace FrostFS.SDK.ClientV2; // clientWrapper is used by default, alternative implementations are intended for testing purposes only. -public class ClientWrapper +public class ClientWrapper : ClientStatusMonitor { private readonly object _lock = new(); - public ClientWrapper(WrapperPrm wrapperPrm) + private SessionCache sessionCache; + + + internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address) { WrapperPrm = wrapperPrm; - StatusMonitor = new ClientStatusMonitor(wrapperPrm.Logger, wrapperPrm.Address, wrapperPrm.ErrorThreshold); + ErrorThreshold = wrapperPrm.ErrorThreshold; - try - { - Client = new FrostFSClient(WrapperPrm); - StatusMonitor.SetHealthy(); - } - catch (FrostFsException) - { - } + sessionCache = pool.SessionCache; + Client = new FrostFSClient(WrapperPrm, sessionCache); } internal FrostFSClient? Client { get; private set; } internal WrapperPrm WrapperPrm { get; } - internal ClientStatusMonitor StatusMonitor { get; } - internal FrostFSClient? GetClient() { lock (_lock) { - if (StatusMonitor.IsHealthy()) + if (IsHealthy()) { return Client; } @@ -44,21 +42,29 @@ public class ClientWrapper // dial establishes a connection to the server from the FrostFS network. // Returns an error describing failure reason. If failed, the client // SHOULD NOT be used. - internal async Task Dial(CallContext ctx) + internal async Task Dial(CallContext ctx) { - var client = GetClient(); + var client = GetClient() ?? throw new FrostFsInvalidObjectException("pool client unhealthy"); - if (client == null) - return "pool client unhealthy"; + await client.Dial(ctx).ConfigureAwait(false); + } - var result = await client.Dial(ctx).ConfigureAwait(false); - if (!string.IsNullOrEmpty(result)) + internal void HandleError(Exception ex) + { + if (ex is FrostFsResponseException responseException && responseException.Status != null) { - StatusMonitor.SetUnhealthyOnDial(); - return result; + switch (responseException.Status.Code) + { + case FrostFsStatusCode.Internal: + case FrostFsStatusCode.WrongMagicNumber: + case FrostFsStatusCode.SignatureVerificationFailure: + case FrostFsStatusCode.NodeUnderMaintenance: + IncErrorRate(); + return; + } } - return null; + IncErrorRate(); } private async Task ScheduleGracefulClose() @@ -79,31 +85,31 @@ public class ClientWrapper try { - var prmNodeInfo = new PrmNodeInfo { Context = ctx }; + var prmNodeInfo = new PrmNodeInfo(ctx); var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); return false; } - catch (FrostFsException) + catch (RpcException) { wasHealthy = true; } // if connection is dialed before, to avoid routine/connection leak, // pool has to close it and then initialize once again. - if (StatusMonitor.IsDialed()) + if (IsDialed()) { await ScheduleGracefulClose().ConfigureAwait(false); } #pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually - FrostFSClient client = new(WrapperPrm); + FrostFSClient client = new(WrapperPrm, sessionCache); #pragma warning restore CA2000 //TODO: set additioanl params var error = await client.Dial(ctx).ConfigureAwait(false); if (!string.IsNullOrEmpty(error)) { - StatusMonitor.SetUnhealthyOnDial(); + SetUnhealthyOnDial(); return wasHealthy; } @@ -114,22 +120,22 @@ public class ClientWrapper try { - var prmNodeInfo = new PrmNodeInfo { Context = ctx }; + var prmNodeInfo = new PrmNodeInfo(ctx); var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); } catch (FrostFsException) { - StatusMonitor.SetUnhealthy(); + SetUnhealthy(); return wasHealthy; } - StatusMonitor.SetHealthy(); + SetHealthy(); return !wasHealthy; } internal void IncRequests(ulong elapsed, MethodIndex method) { - var methodStat = StatusMonitor.Methods[(int)method]; + var methodStat = Methods[(int)method]; methodStat.IncRequests(elapsed); } diff --git a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs b/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs deleted file mode 100644 index 813d373..0000000 --- a/src/FrostFS.SDK.ClientV2/Poll/DialOptions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FrostFS.SDK.ClientV2; - -public class DialOptions -{ - public bool Block { get; set; } - - public bool ReturnLastError { get; set; } - - public ulong Timeout { get; set; } - - public string? Authority { get; set; } - - public bool DisableRetry { get; set; } - - public bool DisableHealthCheck { get; set; } -} diff --git a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs index e05c31a..f02bd9e 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs @@ -1,6 +1,8 @@ using System; using System.Security.Cryptography; +using Grpc.Net.Client; + using Microsoft.Extensions.Logging; namespace FrostFS.SDK.ClientV2; @@ -24,7 +26,7 @@ public class InitParameters public NodeParam[]? NodeParams { get; set; } - public DialOptions[]? DialOptions { get; set; } + public GrpcChannelOptions[]? DialOptions { get; set; } public Func? ClientBuilder { get; set; } diff --git a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs index a535260..104f1f1 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs @@ -21,7 +21,7 @@ internal sealed class InnerPool if (Clients.Length == 1) { var client = Clients[0]; - if (client.StatusMonitor.IsHealthy()) + if (client.IsHealthy()) { return client; } @@ -34,7 +34,7 @@ internal sealed class InnerPool { int index = Sampler.Next(); - if (Clients[index].StatusMonitor.IsHealthy()) + if (Clients[index].IsHealthy()) { return Clients[index]; } diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs index a304e93..2602746 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,6 +12,8 @@ using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; +using Grpc.Core; + using Microsoft.Extensions.Logging; @@ -36,7 +37,7 @@ public partial class Pool : IFrostFSClient private ECDsa Key { get; set; } - private byte[] PublicKey { get; } + private string PublicKey { get; } private OwnerID? _ownerId; private FrostFsOwner? _owner; @@ -65,7 +66,7 @@ public partial class Pool : IFrostFSClient internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); - private SessionCache Cache { get; set; } + internal SessionCache SessionCache { get; set; } private ulong SessionTokenDuration { get; set; } @@ -75,7 +76,7 @@ public partial class Pool : IFrostFSClient private bool disposedValue; - private ILogger? Logger { get; set; } + private ILogger? logger { get; set; } private ulong MaxObjectSize { get; set; } @@ -91,20 +92,20 @@ public partial class Pool : IFrostFSClient if (options.Key == null) { - throw new FrostFsException($"Missed required parameter {nameof(options.Key)}"); + throw new ArgumentException($"Missed required parameter {nameof(options.Key)}"); } var nodesParams = AdjustNodeParams(options.NodeParams); var cache = new SessionCache(options.SessionExpirationDuration); - FillDefaultInitParams(options, cache); + FillDefaultInitParams(options, this); Key = options.Key; - PublicKey = Key.PublicKey(); + PublicKey = $"{Key.PublicKey()}"; - Cache = cache; - Logger = options.Logger; + SessionCache = cache; + logger = options.Logger; SessionTokenDuration = options.SessionExpirationDuration; RebalanceParams = new RebalanceParameters( @@ -148,47 +149,54 @@ public partial class Pool : IFrostFSClient for (int j = 0; j < nodeParams.Addresses.Count; j++) { - var client = ClientBuilder(nodeParams.Addresses[j]); - clients[j] = client; - - var error = await client.Dial(ctx).ConfigureAwait(false); - if (!string.IsNullOrEmpty(error)) - { - Logger?.LogWarning("Failed to build client. Address {Address}, {Error})", client.WrapperPrm.Address, error); - continue; - } - + ClientWrapper? client = null; + bool dialed = false; try { + client = clients[j] = ClientBuilder(nodeParams.Addresses[j]); + + await client.Dial(ctx).ConfigureAwait(false); + dialed = true; + var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false) .ConfigureAwait(false); - var key = FormCacheKey(nodeParams.Addresses[j], Key, false); - _ = Cache.Cache[key] = token; + var key = FormCacheKey(nodeParams.Addresses[j], Key.PrivateKey().ToString()); + _ = SessionCache.Cache[key] = token; + + atLeastOneHealthy = true; } - catch (FrostFsException ex) + catch (RpcException ex) { - client.StatusMonitor.SetUnhealthy(); - Logger?.LogWarning("Failed to create frostfs session token for client. Address {Address}, {Error})", - client.WrapperPrm.Address, ex.Message); + if (!dialed) + client!.SetUnhealthyOnDial(); + else + client!.SetUnhealthy(); - continue; + if (logger != null) + { + FrostFsMessages.SessionCreationError(logger, client!.WrapperPrm.Address, ex.Message); + } + } + catch (FrostFsInvalidObjectException) + { + break; } - - atLeastOneHealthy = true; } var sampler = new Sampler(nodeParams.Weights.ToArray()); inner[i] = new InnerPool(sampler, clients); + + i++; } if (!atLeastOneHealthy) - return "at least one node must be healthy"; + return "At least one node must be healthy"; InnerPools = inner; - var res = await GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false); + var res = await GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false); MaxObjectSize = res.MaxObjectSize; @@ -252,7 +260,7 @@ public partial class Pool : IFrostFSClient return adjusted; } - private static void FillDefaultInitParams(InitParameters parameters, SessionCache cache) + private static void FillDefaultInitParams(InitParameters parameters, Pool pool) { if (parameters.SessionExpirationDuration == 0) parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration; @@ -275,8 +283,8 @@ public partial class Pool : IFrostFSClient if (parameters.NodeStreamTimeout <= 0) parameters.NodeStreamTimeout = defaultStreamTimeout; - if (cache.TokenDuration == 0) - cache.TokenDuration = defaultSessionTokenExpirationDuration; + if (parameters.SessionExpirationDuration == 0) + parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration; parameters.ClientBuilder ??= new Func((address) => { @@ -291,29 +299,29 @@ public partial class Pool : IFrostFSClient GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout }; - return new ClientWrapper(wrapperPrm); + return new ClientWrapper(wrapperPrm, pool); } ); } - private FrostFSClient? Сonnection() + private ClientWrapper Сonnection() { foreach (var pool in InnerPools!) { var client = pool.Connection(); if (client != null) { - return client.Client; + return client; } } - return null; + throw new FrostFsException("Cannot find alive client"); } private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) { var client = cw.Client; - var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings { Context = ctx }).ConfigureAwait(false); + var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false); var epoch = networkInfo.Epoch; @@ -321,17 +329,14 @@ public partial class Pool : IFrostFSClient ? ulong.MaxValue : epoch + duration; - var prmSessionCreate = new PrmSessionCreate(exp) { Context = ctx }; + var prmSessionCreate = new PrmSessionCreate(exp, ctx); return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false); } - private static string FormCacheKey(string address, ECDsa key, bool clientCut) + internal static string FormCacheKey(string address, string key) { - var k = key.PrivateKey; - var stype = clientCut ? "client" : "server"; - - return $"{address}{stype}{k}"; + return $"{address}{key}"; } public void Close() @@ -343,7 +348,7 @@ public partial class Pool : IFrostFSClient // close all clients foreach (var innerPool in InnerPools) foreach (var client in innerPool.Clients) - if (client.StatusMonitor.IsDialed()) + if (client.IsDialed()) client.Client?.Close(); } } @@ -355,7 +360,7 @@ public partial class Pool : IFrostFSClient for (int i = 0; i < RebalanceParams.NodesParams.Length; i++) { - var parameters = this.RebalanceParams.NodesParams[i]; + var parameters = RebalanceParams.NodesParams[i]; buffers[i] = new double[parameters.Weights.Count]; Task.Run(async () => @@ -405,25 +410,27 @@ public partial class Pool : IFrostFSClient try { // check timeout settings - changed = await client.RestartIfUnhealthy(ctx).ConfigureAwait(false); + changed = await client!.RestartIfUnhealthy(ctx).ConfigureAwait(false); healthy = true; bufferWeights[j] = options.NodesParams[poolIndex].Weights[j]; } + // TODO: specify catch (FrostFsException e) { error = e.Message; bufferWeights[j] = 0; - Cache.DeleteByPrefix(client.StatusMonitor.Address); + SessionCache.DeleteByPrefix(client.Address); } if (changed) { - StringBuilder fields = new($"address {client.StatusMonitor.Address}, healthy {healthy}"); - if (string.IsNullOrEmpty(error)) + if (!string.IsNullOrEmpty(error)) { - fields.Append($", reason {error}"); - Logger?.Log(LogLevel.Warning, "Health has changed: {Fields}", fields.ToString()); + if (logger != null) + { + FrostFsMessages.HealthChanged(logger, client.Address, healthy, error!); + } Interlocked.Exchange(ref healthyChanged, 1); } @@ -443,6 +450,8 @@ public partial class Pool : IFrostFSClient } } + + // TODO: remove private bool CheckSessionTokenErr(Exception error, string address) { if (error == null) @@ -452,7 +461,7 @@ public partial class Pool : IFrostFSClient if (error is SessionNotFoundException || error is SessionExpiredException) { - this.Cache.DeleteByPrefix(address); + this.SessionCache.DeleteByPrefix(address); return true; } @@ -463,14 +472,13 @@ public partial class Pool : IFrostFSClient { if (InnerPools == null) { - throw new InvalidObjectException(nameof(Pool)); + throw new FrostFsInvalidObjectException(nameof(Pool)); } var statistics = new Statistic(); foreach (var inner in InnerPools) { - int nodeIndex = 0; int valueIndex = 0; var nodes = new string[inner.Clients.Length]; @@ -478,20 +486,22 @@ public partial class Pool : IFrostFSClient { foreach (var client in inner.Clients) { - if (client.StatusMonitor.IsHealthy()) + if (client.IsHealthy()) { - nodes[valueIndex++] = client.StatusMonitor.Address; + nodes[valueIndex] = client.Address; } var node = new NodeStatistic { - Address = client.StatusMonitor.Address, - Methods = client.StatusMonitor.MethodsStatus(), - OverallErrors = client.StatusMonitor.GetOverallErrorRate(), - CurrentErrors = client.StatusMonitor.GetCurrentErrorRate() + Address = client.Address, + Methods = client.MethodsStatus(), + OverallErrors = client.GetOverallErrorRate(), + CurrentErrors = client.GetCurrentErrorRate() }; - statistics.Nodes[nodeIndex++] = node; + statistics.Nodes.Add(node); + + valueIndex++; statistics.OverallErrors += node.OverallErrors; } @@ -508,120 +518,234 @@ public partial class Pool : IFrostFSClient public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetNetmapSnapshotAsync(args).ConfigureAwait(false); + var client = Сonnection(); + + args ??= new(); + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false); } public async Task GetNodeInfoAsync(PrmNodeInfo? args = null) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetNodeInfoAsync(args).ConfigureAwait(false); + var client = Сonnection(); + + args ??= new(); + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false); } public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetNetworkSettingsAsync(args).ConfigureAwait(false); + var client = Сonnection(); + + args ??= new(); + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false); } public async Task CreateSessionAsync(PrmSessionCreate args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.CreateSessionAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.CreateSessionAsync(args).ConfigureAwait(false); } public async Task AddChainAsync(PrmApeChainAdd args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.AddChainAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.AddChainAsync(args).ConfigureAwait(false); } public async Task RemoveChainAsync(PrmApeChainRemove args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - await client.RemoveChainAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + await client.Client!.RemoveChainAsync(args).ConfigureAwait(false); } public async Task ListChainAsync(PrmApeChainList args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.ListChainAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.ListChainAsync(args).ConfigureAwait(false); } public async Task GetContainerAsync(PrmContainerGet args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetContainerAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetContainerAsync(args).ConfigureAwait(false); } public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return client.ListContainersAsync(args); + var client = Сonnection(); + + args ??= new(); + args.Context.PoolErrorHandler = client.HandleError; + + return client.Client!.ListContainersAsync(args); } public async Task CreateContainerAsync(PrmContainerCreate args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.CreateContainerAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.CreateContainerAsync(args).ConfigureAwait(false); } public async Task DeleteContainerAsync(PrmContainerDelete args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - await client.DeleteContainerAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + await client.Client!.DeleteContainerAsync(args).ConfigureAwait(false); } public async Task GetObjectHeadAsync(PrmObjectHeadGet args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetObjectHeadAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetObjectHeadAsync(args).ConfigureAwait(false); } public async Task GetObjectAsync(PrmObjectGet args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetObjectAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetObjectAsync(args).ConfigureAwait(false); } public async Task PutObjectAsync(PrmObjectPut args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.PutObjectAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.PutObjectAsync(args).ConfigureAwait(false); } public async Task PutSingleObjectAsync(PrmSingleObjectPut args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.PutSingleObjectAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false); } public async Task DeleteObjectAsync(PrmObjectDelete args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - await client.DeleteObjectAsync(args).ConfigureAwait(false); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + await client.Client!.DeleteObjectAsync(args).ConfigureAwait(false); } public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return client.SearchObjectsAsync(args); + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Сonnection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return client.Client!.SearchObjectsAsync(args); } - public async Task GetBalanceAsync(PrmBalance? args = null) + public async Task GetBalanceAsync(PrmBalance? args) { - var client = Сonnection() ?? throw new FrostFsException("Cannot find alive client"); - return await client.GetBalanceAsync(args).ConfigureAwait(false); - } + var client = Сonnection(); - public bool RestartIfUnhealthy(CallContext ctx) - { - throw new NotImplementedException(); - } + args ??= new(); + args.Context.PoolErrorHandler = client.HandleError; - public bool IsHealthy() - { - throw new NotImplementedException(); + return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false); } protected virtual void Dispose(bool disposing) diff --git a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs index b1bc9b9..a443b2d 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs @@ -3,7 +3,7 @@ public class RebalanceParameters( NodesParam[] nodesParams, ulong nodeRequestTimeout, - ulong clientRebalanceInterval, + ulong clientRebalanceInterval, ulong sessionExpirationDuration) { public NodesParam[] NodesParams { get; set; } = nodesParams; diff --git a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs index eccb0a5..f65046f 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs @@ -3,18 +3,13 @@ using System.Collections; namespace FrostFS.SDK.ClientV2; -internal struct SessionCache +internal struct SessionCache(ulong sessionExpirationDuration) { - public SessionCache(ulong sessionExpirationDuration) - { - TokenDuration = sessionExpirationDuration; - } - internal Hashtable Cache { get; } = []; internal ulong CurrentEpoch { get; set; } - internal ulong TokenDuration { get; set; } + internal ulong TokenDuration { get; set; } = sessionExpirationDuration; internal void DeleteByPrefix(string prefix) { diff --git a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs index 94dda54..b5fa42b 100644 --- a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs @@ -10,7 +10,7 @@ internal sealed class AccountingServiceProvider : ContextAccessor internal AccountingServiceProvider( AccountingService.AccountingServiceClient? accountingServiceClient, - EnvironmentContext context) + ClientContext context) : base(context) { _accountingServiceClient = accountingServiceClient; diff --git a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs index 1c5ab8c..d05e636 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Frostfs.V2.Ape; @@ -9,7 +10,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor { private readonly APEManagerService.APEManagerServiceClient? _apeManagerServiceClient; - internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, EnvironmentContext context) + internal ApeManagerServiceProvider(APEManagerService.APEManagerServiceClient? apeManagerServiceClient, ClientContext context) : base(context) { _apeManagerServiceClient = apeManagerServiceClient; @@ -18,10 +19,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task AddChainAsync(PrmApeChainAdd args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); AddChainRequest request = new() { @@ -45,10 +46,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task RemoveChainAsync(PrmApeChainRemove args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); RemoveChainRequest request = new() { @@ -70,10 +71,10 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task ListChainAsync(PrmApeChainList args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); ListChainsRequest request = new() { diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs index 14a3e2d..4e00a7d 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs @@ -12,20 +12,28 @@ using FrostFS.Session; namespace FrostFS.SDK.ClientV2; -internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, EnvironmentContext envCtx) : ContextAccessor(envCtx), ISessionProvider +internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider { - readonly SessionProvider sessions = new(envCtx); + private SessionProvider? sessions; public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { + sessions ??= new(ClientContext); + + if (ClientContext.SessionCache.Cache != null && + ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey)) + { + return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey]; + } + return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } internal async Task GetContainerAsync(PrmContainerGet args) { - GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context!); + GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context); - var response = await service.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); @@ -35,13 +43,13 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; - ctx.OwnerId ??= EnvironmentContext.Owner; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.OwnerId ??= ClientContext.Owner; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); if (ctx.OwnerId == null) - throw new InvalidObjectException(nameof(ctx.OwnerId)); + throw new ArgumentException(nameof(ctx.OwnerId)); var request = new ListRequest { @@ -74,11 +82,11 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService grpcContainer.Version ??= ctx.Version?.ToMessage(); if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); if (grpcContainer.OwnerId == null) - throw new InvalidObjectException(nameof(grpcContainer.OwnerId)); + throw new ArgumentException(nameof(grpcContainer.OwnerId)); if (grpcContainer.Version == null) - throw new InvalidObjectException(nameof(grpcContainer.Version)); + throw new ArgumentException(nameof(grpcContainer.Version)); var request = new PutRequest { @@ -114,7 +122,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService { var ctx = args.Context!; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new DeleteRequest { @@ -150,7 +158,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx) { if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(ctx), "Key is null"); var request = new GetRequest { @@ -207,7 +215,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService await Task.Delay(waitParams.PollInterval).ConfigureAwait(false); } - catch (ResponseException ex) + catch (FrostFsResponseException ex) { if (DateTime.UtcNow >= deadLine) throw new TimeoutException(); diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs index 91645fb..0241e31 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -12,7 +13,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor { private readonly NetmapService.NetmapServiceClient netmapServiceClient; - internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, EnvironmentContext context) + internal NetmapServiceProvider(NetmapService.NetmapServiceClient netmapServiceClient, ClientContext context) : base(context) { this.netmapServiceClient = netmapServiceClient; @@ -20,8 +21,8 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetworkSettingsAsync(CallContext ctx) { - if (EnvironmentContext.NetworkSettings != null) - return EnvironmentContext.NetworkSettings; + if (ClientContext.NetworkSettings != null) + return ClientContext.NetworkSettings; var response = await GetNetworkInfoAsync(ctx).ConfigureAwait(false); @@ -38,7 +39,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor SetNetworksParam(param, settings); } - EnvironmentContext.NetworkSettings = settings; + ClientContext.NetworkSettings = settings; return settings; } @@ -46,10 +47,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new LocalNodeInfoRequest { @@ -59,8 +60,6 @@ internal sealed class NetmapServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ctx.Key); - - var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); @@ -70,10 +69,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetworkInfoAsync(CallContext ctx) { - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(ctx), "Key is null"); var request = new NetworkInfoRequest(); @@ -91,10 +90,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new NetmapSnapshotRequest(); diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index 6ab3362..bcea7c7 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -15,30 +15,32 @@ using Google.Protobuf; namespace FrostFS.SDK.ClientV2; -internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider +internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientContext clientCtx) + : ContextAccessor(clientCtx), ISessionProvider { - private readonly SessionProvider sessions; - private ObjectService.ObjectServiceClient client; - - internal ObjectServiceProvider(ObjectService.ObjectServiceClient client, EnvironmentContext env) - : base(env) - { - this.sessions = new(EnvironmentContext); - this.client = client; - } + private SessionProvider? sessions; + private readonly ObjectService.ObjectServiceClient client = client; public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { + sessions ??= new(ClientContext); + + if (ClientContext.SessionCache.Cache != null && + ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey)) + { + return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey]; + } + return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); } internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new HeadRequest { @@ -74,10 +76,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new GetRequest { @@ -108,10 +110,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; - ctx.Key ??= EnvironmentContext.Key?.ECDsaKey; + ctx.Key ??= ClientContext.Key?.ECDsaKey; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new DeleteRequest { @@ -145,7 +147,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider var ctx = args.Context!; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var request = new SearchRequest { @@ -183,10 +185,10 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider throw new ArgumentNullException(nameof(args)); if (args.Header == null) - throw new ArgumentException(nameof(args.Header)); + throw new ArgumentNullException(nameof(args), "Header is null"); if (args.Payload == null) - throw new ArgumentException(nameof(args.Payload)); + throw new ArgumentNullException(nameof(args), "Payload is null"); if (args.ClientCut) return await PutClientCutObject(args).ConfigureAwait(false); @@ -206,7 +208,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider var ctx = args.Context!; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx); @@ -254,7 +256,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider if (args.MaxObjectSizeCache == 0) { - var networkSettings = await EnvironmentContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings() { Context = ctx }) + var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)) .ConfigureAwait(false); args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; @@ -306,7 +308,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds); - _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject) { Context = args.Context }).ConfigureAwait(false); + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, args.Context)).ConfigureAwait(false); var parentHeader = args.Header.GetHeader(); @@ -331,7 +333,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider { var ctx = args.Context!; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); var payload = args.Payload!; @@ -352,7 +354,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider } else { - chunkBuffer = EnvironmentContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); isRentBuffer = true; } @@ -409,7 +411,7 @@ internal sealed class ObjectServiceProvider : ContextAccessor, ISessionProvider var header = args.Header!; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new ArgumentNullException(nameof(args), "Key is null"); header.OwnerId ??= ctx.OwnerId; header.Version ??= ctx.Version; diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs index 1dfe53f..73418d0 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs @@ -10,7 +10,7 @@ internal sealed class SessionServiceProvider : ContextAccessor { private readonly SessionService.SessionServiceClient? _sessionServiceClient; - internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, EnvironmentContext context) + internal SessionServiceProvider(SessionService.SessionServiceClient? sessionServiceClient, ClientContext context) : base(context) { _sessionServiceClient = sessionServiceClient; @@ -20,7 +20,7 @@ internal sealed class SessionServiceProvider : ContextAccessor { var ctx = args.Context!; - ctx.OwnerId ??= EnvironmentContext.Owner; + ctx.OwnerId ??= ClientContext.Owner; var request = new CreateRequest { diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs index 3fb7674..70fc9c9 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs @@ -1,6 +1,6 @@ namespace FrostFS.SDK.ClientV2; -internal class ContextAccessor(EnvironmentContext context) +internal class ContextAccessor(ClientContext context) { - protected EnvironmentContext EnvironmentContext { get; set; } = context; + protected ClientContext ClientContext { get; set; } = context; } diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs index 51fe337..e18de09 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs @@ -7,14 +7,13 @@ internal interface ISessionProvider ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx); } -internal sealed class SessionProvider(EnvironmentContext envCtx) +internal sealed class SessionProvider(ClientContext envCtx) { - // TODO: implement cache for session in the next iteration public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) { if (args.SessionToken is null) { - return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue) { Context = ctx }) + return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)) .ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs b/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs similarity index 62% rename from src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs rename to src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs index d0596c9..c0a1db3 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/EnvironmentContext.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs @@ -2,16 +2,21 @@ using System; using System.Buffers; using System.Security.Cryptography; +using FrostFS.SDK.Cryptography; + using Grpc.Net.Client; namespace FrostFS.SDK.ClientV2; -public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable +public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { private ArrayPool? _arrayPool; + private string? sessionKey; internal FrostFsOwner? Owner { get; } = owner; + internal string? Address { get; } = channel.Target; + internal GrpcChannel Channel { get; private set; } = channel; internal FrostFsVersion Version { get; } = version; @@ -22,6 +27,21 @@ public class EnvironmentContext(FrostFSClient client, ECDsa? key, FrostFsOwner? internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null; + internal SessionCache SessionCache { get; set; } + + internal string? SessionCacheKey + { + get + { + if (sessionKey == null && Key != null && Address != null) + { + sessionKey = Pool.FormCacheKey(Address, Key.ECDsaKey.PrivateKey().ToString()); + } + + return sessionKey; + } + } + /// /// Custom pool is used for predefined sizes of buffers like grpc chunk /// diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs index b1ad4f7..7df2343 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs @@ -17,13 +17,13 @@ public sealed class ObjectReader(AsyncServerStreamingCall call) : I internal async Task ReadHeader() { if (!await Call.ResponseStream.MoveNext().ConfigureAwait(false)) - throw new InvalidOperationException("unexpected end of stream"); + throw new FrostFsStreamException("unexpected end of stream"); var response = Call.ResponseStream.Current; Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Init) - throw new InvalidOperationException("unexpected message type"); + throw new FrostFsStreamException("unexpected message type"); return new Object.Object { @@ -41,7 +41,7 @@ public sealed class ObjectReader(AsyncServerStreamingCall call) : I Verifier.CheckResponse(response); if (response.Body.ObjectPartCase != GetResponse.Types.Body.ObjectPartOneofCase.Chunk) - throw new InvalidOperationException("unexpected message type"); + throw new FrostFsStreamException("unexpected message type"); return response.Body.Chunk.Memory; } diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs index f4d659c..91df2ad 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs @@ -59,7 +59,7 @@ internal static class ObjectTools return; if (ctx.Key == null) - throw new InvalidObjectException(nameof(ctx.Key)); + throw new FrostFsInvalidObjectException(nameof(ctx.Key)); grpcHeader.Split = new Header.Types.Split { diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs index 2896209..c87ef0a 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs @@ -122,7 +122,7 @@ public static class Verifier var status = resp.MetaHeader.Status.ToModel(); if (status != null && !status.IsSuccess) - throw new ResponseException(status); + throw new FrostFsResponseException(status); } /// @@ -137,6 +137,6 @@ public static class Verifier } if (!request.Verify()) - throw new FormatException($"invalid response, type={request.GetType()}"); + throw new FrostFsResponseException($"invalid response, type={request.GetType()}"); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/CallbackInterceptor.cs b/src/FrostFS.SDK.Tests/CallbackInterceptor.cs new file mode 100644 index 0000000..c4eb726 --- /dev/null +++ b/src/FrostFS.SDK.Tests/CallbackInterceptor.cs @@ -0,0 +1,33 @@ +using Grpc.Core; +using Grpc.Core.Interceptors; + +namespace FrostFS.SDK.SmokeTests; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", + Justification = "parameters are provided by GRPC infrastructure")] +public class CallbackInterceptor(Action? callback = null) : Interceptor +{ + public override AsyncUnaryCall AsyncUnaryCall( + TRequest request, + ClientInterceptorContext context, + AsyncUnaryCallContinuation continuation) + { + var call = continuation(request, context); + + return new AsyncUnaryCall( + HandleUnaryResponse(call), + call.ResponseHeadersAsync, + call.GetStatus, + call.GetTrailers, + call.Dispose); + } + + private async Task HandleUnaryResponse(AsyncUnaryCall call) + { + var response = await call; + + callback?.Invoke($"elapsed"); + + return response; + } +} diff --git a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs b/src/FrostFS.SDK.Tests/MetricsInterceptor.cs deleted file mode 100644 index 9f5d824..0000000 --- a/src/FrostFS.SDK.Tests/MetricsInterceptor.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Diagnostics; - -using Grpc.Core; -using Grpc.Core.Interceptors; - -namespace FrostFS.SDK.SmokeTests; - -public class MetricsInterceptor() : Interceptor -{ - public override AsyncUnaryCall AsyncUnaryCall( - TRequest request, - ClientInterceptorContext context, - AsyncUnaryCallContinuation continuation) - { - ArgumentNullException.ThrowIfNull(continuation); - - using var call = continuation(request, context); - - return new AsyncUnaryCall( - HandleUnaryResponse(call), - call.ResponseHeadersAsync, - call.GetStatus, - call.GetTrailers, - call.Dispose); - } - - private static async Task HandleUnaryResponse(AsyncUnaryCall call) - { - var watch = new Stopwatch(); - watch.Start(); - - var response = await call.ResponseAsync.ConfigureAwait(false); - - watch.Stop(); - - // Do something with call info - // var elapsed = watch.ElapsedTicks * 1_000_000/Stopwatch.Frequency; - - return response; - } -} diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index f09cafb..99fa2b2 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -28,19 +28,16 @@ public class NetworkTest : NetworkTestsBase Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]); Mocker.Parameters.Add("MaintenanceModeAllowed", [1]); - var param = new PrmNetworkSettings(); - - if (useContext) - { - param.Context = new CallContext + var param = useContext ? + new PrmNetworkSettings(new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), OwnerId = OwnerId, Key = ECDsaKey, Version = Version - }; - } + }) + : new PrmNetworkSettings(); var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); @@ -116,12 +113,11 @@ public class NetworkTest : NetworkTestsBase Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; - var param = new PrmNetmapSnapshot(); + PrmNetmapSnapshot param; if (useContext) { - param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new CallContext + var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -129,6 +125,14 @@ public class NetworkTest : NetworkTestsBase Key = ECDsaKey, Version = Version }; + + param = new(ctx); + param.XHeaders.Add("headerKey1", "headerValue1"); + + } + else + { + param = new(); } var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); @@ -208,12 +212,11 @@ public class NetworkTest : NetworkTestsBase Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; - var param = new PrmNodeInfo(); + PrmNodeInfo param; if (useContext) { - param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new CallContext + var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -221,6 +224,14 @@ public class NetworkTest : NetworkTestsBase Key = ECDsaKey, Version = Version }; + + param = new(ctx); + param.XHeaders.Add("headerKey1", "headerValue1"); + + } + else + { + param = new(); } var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 07fa8e9..85b42ca 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -32,7 +32,7 @@ public class ObjectTest : ObjectTestsBase var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx); - var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId) { Context = ctx }); + var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId, ctx)); Assert.NotNull(result); @@ -50,7 +50,7 @@ public class ObjectTest : ObjectTestsBase [Fact] public async void PutObjectTest() { - Mocker.ResultObjectIds.Add(SHA256.HashData([])); + Mocker.ResultObjectIds!.Add(SHA256.HashData([])); Random rnd = new(); var bytes = new byte[1024]; @@ -107,7 +107,7 @@ public class ObjectTest : ObjectTestsBase rnd.NextBytes(objIds.ElementAt(2)); foreach (var objId in objIds) - Mocker.ResultObjectIds.Add(objId); + Mocker.ResultObjectIds!.Add(objId); var result = await GetClient().PutObjectAsync(param); diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs index 87bf106..01320b5 100644 --- a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs @@ -23,16 +23,6 @@ public class PoolSmokeTests : SmokeTestsBase Key = keyString.LoadWif(), NodeParams = [new(1, this.url, 100.0f)], - DialOptions = [new() - { - Authority = "", - Block = false, - DisableHealthCheck = false, - DisableRetry = false, - ReturnLastError = true, - Timeout = 30_000_000 - } - ], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, Logger = null @@ -85,6 +75,44 @@ public class PoolSmokeTests : SmokeTestsBase Assert.Equal(9, result.Attributes.Count); } + [Fact] + public async void NodeInfoStatisticsTwoNodesTest() + { + var options = new InitParameters + { + Key = keyString.LoadWif(), + NodeParams = [ + new(1, this.url, 100.0f), + new(2, this.url.Replace('0', '1'), 100.0f) + ], + ClientBuilder = null, + GracefulCloseOnSwitchTimeout = 30_000_000, + Logger = null + }; + + using var pool = new Pool(options); + + var callbackText = string.Empty; + + var ctx = new CallContext + { + Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" + }; + + var error = await pool.Dial(ctx).ConfigureAwait(true); + + Assert.Null(error); + + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + var result = await client.GetNodeInfoAsync(); + + var statistics = pool.Statistic(); + + Assert.False(string.IsNullOrEmpty(callbackText)); + Assert.Contains(" took ", callbackText, StringComparison.Ordinal); + } + [Fact] public async void NodeInfoStatisticsTest() { @@ -308,31 +336,29 @@ public class PoolSmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])) - { - Context = ctx - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); var createdContainer = await pool.CreateContainerAsync(createContainerParam); - var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx }); + var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer)); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut + var ctx1 = new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; + + var param = new PrmObjectPut(ctx1) { Header = new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), - ClientCut = false, - Context = new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - } + ClientCut = false }; var objectId = await pool.PutObjectAsync(param); @@ -400,19 +426,16 @@ public class PoolSmokeTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - Context = ctx - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx); var container = await pool.CreateContainerAsync(createContainerParam); - var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container) { Context = ctx }); + var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container, ctx)); Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut + var param = new PrmObjectPut(new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) }) { Header = new FrostFsObjectHeader( containerId: container, @@ -420,10 +443,6 @@ public class PoolSmokeTests : SmokeTestsBase [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, - Context = new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }, SessionToken = token }; @@ -496,10 +515,11 @@ public class PoolSmokeTests : SmokeTestsBase var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(10), - Interceptors = new([new MetricsInterceptor()]) }; - var container = await pool.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); + ctx.Interceptors.Add(new CallbackInterceptor()); + + var container = await pool.GetContainerAsync(new PrmContainerGet(containerId, ctx)); Assert.NotNull(container); @@ -520,7 +540,7 @@ public class PoolSmokeTests : SmokeTestsBase var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter))) { hasObject = true; @@ -551,21 +571,10 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); - var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - - IAsyncEnumerator? enumerator = null; - do + await foreach (var cid in pool.ListContainersAsync()) { - if (deadline <= DateTime.UtcNow) - { - Assert.Fail("Containers exist"); - break; - } - - enumerator = pool.ListContainersAsync().GetAsyncEnumerator(); - await Task.Delay(500); + Assert.Fail($"Container {cid.GetValue()} exist"); } - while (await enumerator!.MoveNextAsync()); } private static byte[] GetRandomBytes(int size) diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index e343c82..8009f5c 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -14,12 +14,11 @@ public class SessionTest : SessionTestsBase public async void CreateSessionTest(bool useContext) { var exp = 100u; - var param = new PrmSessionCreate(exp); + PrmSessionCreate param; if (useContext) { - param.XHeaders.Add("headerKey1", "headerValue1"); - param.Context = new CallContext + var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, Timeout = TimeSpan.FromSeconds(20), @@ -27,6 +26,14 @@ public class SessionTest : SessionTestsBase Key = ECDsaKey, Version = Mocker.Version }; + + param = new PrmSessionCreate(exp, ctx); + + param.XHeaders.Add("headerKey1", "headerValue1"); + } + else + { + param = new PrmSessionCreate(exp); } var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs index 54868d6..426aa9b 100644 --- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs @@ -26,7 +26,7 @@ public class SmokeClientTests : SmokeTestsBase ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); - PrmBalance? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; + PrmBalance? prm = isSingleOnwerClient ? default : new(Ctx); var result = await client.GetBalanceAsync(prm); } @@ -38,7 +38,7 @@ public class SmokeClientTests : SmokeTestsBase { using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); - PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; + PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new(Ctx); var result = await client.GetNetmapSnapshotAsync(prm); Assert.True(result.Epoch > 0); @@ -59,9 +59,11 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(true)] public async void NodeInfoTest(bool isSingleOnwerClient) { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = isSingleOnwerClient + ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) + : FrostFSClient.GetInstance(GetOptions(this.url)); - PrmNodeInfo? prm = isSingleOnwerClient ? default : new() { Context = Ctx }; + PrmNodeInfo? prm = isSingleOnwerClient ? default : new(Ctx); var result = await client.GetNodeInfoAsync(prm); @@ -93,7 +95,7 @@ public class SmokeClientTests : SmokeTestsBase { using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); - PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100) { Context = Ctx }; + PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100, Ctx); var token = await client.CreateSessionAsync(prm); @@ -262,31 +264,27 @@ public class SmokeClientTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])) - { - Context = ctx - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); var createdContainer = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer) { Context = ctx }); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx)); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut + var param = new PrmObjectPut(new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }) { Header = new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), - ClientCut = false, - Context = new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - } + ClientCut = false }; var objectId = await client.PutObjectAsync(param); @@ -348,19 +346,19 @@ public class SmokeClientTests : SmokeTestsBase }; var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - Context = ctx - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx); var container = await client.CreateContainerAsync(createContainerParam); - var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container) { Context = ctx }); + var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container, ctx)); Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut + var param = new PrmObjectPut(new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }) { Header = new FrostFsObjectHeader( containerId: container, @@ -368,10 +366,6 @@ public class SmokeClientTests : SmokeTestsBase [new FrostFsAttributePair("fileName", "test")]), Payload = new MemoryStream(bytes), ClientCut = false, - Context = new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }, SessionToken = token }; @@ -437,11 +431,12 @@ public class SmokeClientTests : SmokeTestsBase var ctx = new CallContext { - Timeout = TimeSpan.FromSeconds(10), - Interceptors = new([new MetricsInterceptor()]) + Timeout = TimeSpan.FromSeconds(10) }; - var container = await client.GetContainerAsync(new PrmContainerGet(containerId) { Context = ctx }); + ctx.Interceptors.Add(new CallbackInterceptor()); + + var container = await client.GetContainerAsync(new PrmContainerGet(containerId, ctx)); Assert.NotNull(container); @@ -462,7 +457,7 @@ public class SmokeClientTests : SmokeTestsBase var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, filter))) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter))) { hasObject = true; @@ -510,6 +505,38 @@ public class SmokeClientTests : SmokeTestsBase while (await enumerator!.MoveNextAsync()); } + [Fact] + public async void NodeInfoCallbackAndInterceptorTest() + { + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + bool callbackInvoked = false; + bool intercepterInvoked = false; + + var ctx = new CallContext + { + Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }) + }; + + ctx.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); + + var result = await client.GetNodeInfoAsync(new PrmNodeInfo(ctx)); + + Assert.True(callbackInvoked); + Assert.True(intercepterInvoked); + + 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); + } + private static byte[] GetRandomBytes(int size) { Random rnd = new(); From bff8d6786767a8ece329676427a26004abb4685c Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 1 Nov 2024 10:41:17 +0300 Subject: [PATCH 26/65] [#24] Client: Implement pool part2 Unicode fix Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/Poll/Pool.cs | 39 ++++++++++++++------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs index 2602746..3e20634 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs +++ b/src/FrostFS.SDK.ClientV2/Poll/Pool.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -304,7 +305,7 @@ public partial class Pool : IFrostFSClient ); } - private ClientWrapper Сonnection() + private ClientWrapper Connection() { foreach (var pool in InnerPools!) { @@ -518,7 +519,7 @@ public partial class Pool : IFrostFSClient public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null) { - var client = Сonnection(); + var client = Connection(); args ??= new(); args.Context.PoolErrorHandler = client.HandleError; @@ -528,7 +529,7 @@ public partial class Pool : IFrostFSClient public async Task GetNodeInfoAsync(PrmNodeInfo? args = null) { - var client = Сonnection(); + var client = Connection(); args ??= new(); args.Context.PoolErrorHandler = client.HandleError; @@ -538,7 +539,7 @@ public partial class Pool : IFrostFSClient public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null) { - var client = Сonnection(); + var client = Connection(); args ??= new(); args.Context.PoolErrorHandler = client.HandleError; @@ -553,7 +554,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -567,7 +568,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -581,7 +582,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -595,7 +596,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -609,7 +610,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -618,7 +619,7 @@ public partial class Pool : IFrostFSClient public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { - var client = Сonnection(); + var client = Connection(); args ??= new(); args.Context.PoolErrorHandler = client.HandleError; @@ -633,7 +634,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -647,7 +648,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -661,7 +662,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -675,7 +676,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -689,7 +690,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -703,7 +704,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -717,7 +718,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -731,7 +732,7 @@ public partial class Pool : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var client = Сonnection(); + var client = Connection(); args.Context.PoolErrorHandler = client.HandleError; @@ -740,7 +741,7 @@ public partial class Pool : IFrostFSClient public async Task GetBalanceAsync(PrmBalance? args) { - var client = Сonnection(); + var client = Connection(); args ??= new(); args.Context.PoolErrorHandler = client.HandleError; From 003b7fdfdd71fbd293cd96a28467bf2e977ef1a1 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 8 Nov 2024 10:38:50 +0300 Subject: [PATCH 27/65] [#25] Client: Implement Patch and Range methods Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 30 +++ .../Interfaces/IFrostFSClient.cs | 6 + .../Mappers/ContainerId.cs | 11 +- .../Models/Object/FrostFsAddress.cs | 48 ++++ .../Models/Object/FrostFsRange.cs | 27 ++ .../Models/Object/IObjectReader.cs | 2 +- .../Parameters/PrmObjectPatch.cs | 24 ++ .../Parameters/PrmRangeGet.cs | 20 ++ .../Parameters/PrmRangeHashGet.cs | 20 ++ .../{Poll => Pool}/ClientStatusMonitor.cs | 0 .../{Poll => Pool}/ClientWrapper.cs | 4 +- .../{Poll => Pool}/HealthyStatus.cs | 0 .../{Poll => Pool}/IClientStatus.cs | 0 .../{Poll => Pool}/InitParameters.cs | 0 .../{Poll => Pool}/InnerPool.cs | 0 .../{Poll => Pool}/MethodIndex.cs | 0 .../{Poll => Pool}/MethodStatus.cs | 0 .../{Poll => Pool}/NodeParam.cs | 0 .../{Poll => Pool}/NodeStatistic.cs | 0 .../{Poll => Pool}/NodesParam.cs | 0 .../{Poll => Pool}/Pool.cs | 57 +++- .../{Poll => Pool}/RebalanceParameters.cs | 0 .../{Poll => Pool}/RequestInfo.cs | 0 .../{Poll => Pool}/Sampler.cs | 0 .../{Poll => Pool}/SessionCache.cs | 0 .../{Poll => Pool}/Statistic.cs | 0 .../{Poll => Pool}/StatusSnapshot.cs | 0 .../{Poll => Pool}/WorkList.cs | 0 .../{Poll => Pool}/WrapperPrm.cs | 0 .../Services/ObjectServiceProvider.cs | 226 ++++++++++++++-- .../Tools/ObjectReader.cs | 2 +- .../Tools/ObjectStreamer.cs | 10 +- src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs | 38 +++ .../accounting/service.proto | 18 +- src/FrostFS.SDK.ProtosV2/acl/types.proto | 28 +- src/FrostFS.SDK.ProtosV2/netmap/service.proto | 8 +- src/FrostFS.SDK.ProtosV2/netmap/types.proto | 74 +++-- .../object/Extension.Message.cs | 56 ++++ src/FrostFS.SDK.ProtosV2/object/service.proto | 127 ++++++++- src/FrostFS.SDK.ProtosV2/object/types.proto | 14 +- src/FrostFS.SDK.ProtosV2/refs/types.proto | 11 +- .../session/service.proto | 2 +- src/FrostFS.SDK.ProtosV2/session/types.proto | 11 +- src/FrostFS.SDK.ProtosV2/status/types.proto | 19 +- .../tombstone/types.proto | 6 +- .../Mocks/AsyncStreamRangeReaderMock.cs | 43 +++ src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 124 +++++++-- .../Mocks/PatchStreamWriter.cs | 36 +++ src/FrostFS.SDK.Tests/ObjectTest.cs | 119 +++++++++ src/FrostFS.SDK.Tests/SmokeClientTests.cs | 252 +++++++++++++++--- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 2 + 51 files changed, 1338 insertions(+), 137 deletions(-) create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs create mode 100644 src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs create mode 100644 src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/ClientStatusMonitor.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/ClientWrapper.cs (96%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/HealthyStatus.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/IClientStatus.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/InitParameters.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/InnerPool.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/MethodIndex.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/MethodStatus.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/NodeParam.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/NodeStatistic.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/NodesParam.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/Pool.cs (93%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/RebalanceParameters.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/RequestInfo.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/Sampler.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/SessionCache.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/Statistic.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/StatusSnapshot.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/WorkList.cs (100%) rename src/FrostFS.SDK.ClientV2/{Poll => Pool}/WrapperPrm.cs (100%) create mode 100644 src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs create mode 100644 src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs index e13b6b6..830eb94 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs @@ -285,6 +285,25 @@ public class FrostFSClient : IFrostFSClient return service.GetObjectAsync(args); } + public Task GetRangeAsync(PrmRangeGet args) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var service = GetObjectService(args); + return service.GetRangeAsync(args); + } + + public Task>> GetRangeHashAsync(PrmRangeHashGet args) + { + if (args is null) + throw new ArgumentNullException(nameof(args)); + + var service = GetObjectService(args); + return service.GetRangeHashAsync(args); + } + + public Task PutObjectAsync(PrmObjectPut args) { if (args is null) @@ -303,6 +322,17 @@ public class FrostFSClient : IFrostFSClient return service.PutSingleObjectAsync(args); } + public Task PatchObjectAsync(PrmObjectPatch args) + { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var service = GetObjectService(args); + return service.PatchObjectAsync(args); + } + public Task DeleteObjectAsync(PrmObjectDelete args) { if (args is null) diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index e81343b..48ab8e8 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -42,10 +42,16 @@ public interface IFrostFSClient : IDisposable Task GetObjectAsync(PrmObjectGet args); + Task GetRangeAsync(PrmRangeGet args); + + Task>> GetRangeHashAsync(PrmRangeHashGet args); + Task PutObjectAsync(PrmObjectPut args); Task PutSingleObjectAsync(PrmSingleObjectPut args); + Task PatchObjectAsync(PrmObjectPatch args); + Task DeleteObjectAsync(PrmObjectDelete args); IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs index 7a5359d..c4c805c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs @@ -33,7 +33,16 @@ public static class ContainerIdMapper Caches.Containers.Set(containerId, message, _oneHourExpiration); } - return message!; } + + public static FrostFsContainerId ToModel(this ContainerID message) + { + if (message is null) + { + throw new ArgumentNullException(nameof(message)); + } + + return new FrostFsContainerId(Base58.Encode(message.Value.ToByteArray())); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs new file mode 100644 index 0000000..46581d1 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs @@ -0,0 +1,48 @@ +using FrostFS.Refs; +using FrostFS.SDK.ClientV2.Mappers.GRPC; + +namespace FrostFS.SDK; + +public class FrostFsAddress +{ + private FrostFsObjectId? frostFsObjectId; + private FrostFsContainerId? frostFsContainerId; + private ObjectID? objectId; + private ContainerID? containerId; + + public FrostFsAddress(FrostFsContainerId frostFsContainerId, FrostFsObjectId frostFsObjectId) + { + FrostFsObjectId = frostFsObjectId ?? throw new System.ArgumentNullException(nameof(frostFsObjectId)); + FrostFsContainerId = frostFsContainerId ?? throw new System.ArgumentNullException(nameof(frostFsContainerId)); + } + + internal FrostFsAddress(ObjectID objectId, ContainerID containerId) + { + ObjectId = objectId ?? throw new System.ArgumentNullException(nameof(objectId)); + ContainerId = containerId ?? throw new System.ArgumentNullException(nameof(containerId)); + } + + public FrostFsObjectId FrostFsObjectId + { + get => frostFsObjectId ??= objectId!.ToModel(); + set => frostFsObjectId = value; + } + + public FrostFsContainerId FrostFsContainerId + { + get => frostFsContainerId ??= containerId!.ToModel(); + set => frostFsContainerId = value; + } + + public ObjectID ObjectId + { + get => objectId ??= frostFsObjectId!.ToMessage(); + set => objectId = value; + } + + public ContainerID ContainerId + { + get => containerId ??= frostFsContainerId!.ToMessage(); + set => containerId = value; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs new file mode 100644 index 0000000..b50568f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs @@ -0,0 +1,27 @@ +namespace FrostFS.SDK; + +public readonly struct FrostFsRange(ulong offset, ulong length) : System.IEquatable +{ + public ulong Offset { get; } = offset; + + public ulong Length { get; } = length; + + public override readonly bool Equals(object obj) => this == (FrostFsRange)obj; + + public override readonly int GetHashCode() => $"{Offset}{Length}".GetHashCode(); + + public static bool operator ==(FrostFsRange left, FrostFsRange right) + { + return left.Equals(right); + } + + public static bool operator !=(FrostFsRange left, FrostFsRange right) + { + return !(left == right); + } + + public readonly bool Equals(FrostFsRange other) + { + return this == other; + } +} diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs b/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs index 51211fd..8b08e6b 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs @@ -6,5 +6,5 @@ namespace FrostFS.SDK; public interface IObjectReader : IDisposable { - Task?> ReadChunk(CancellationToken cancellationToken = default); + ValueTask?> ReadChunk(CancellationToken cancellationToken = default); } diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs new file mode 100644 index 0000000..de292ea --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs @@ -0,0 +1,24 @@ +using System.IO; + +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmObjectPatch(FrostFsAddress address, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +{ + public FrostFsAddress Address { get; } = address; + + public FrostFsRange Range { get; set; } + + /// + /// A stream with source data + /// + public Stream? Payload { get; set; } + + public FrostFsAttributePair[]? NewAttributes { get; set; } + + public bool ReplaceAttributes { get; set; } + + public int MaxPayloadPatchChunkLength { get; set; } + + /// + public FrostFsSessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs new file mode 100644 index 0000000..5bcad9f --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs @@ -0,0 +1,20 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmRangeGet( + FrostFsContainerId containerId, + FrostFsObjectId objectId, + FrostFsRange range, + bool raw = false, + CallContext? ctx = null) : PrmBase(ctx), ISessionToken +{ + public FrostFsContainerId ContainerId { get; } = containerId; + + public FrostFsObjectId ObjectId { get; } = objectId; + + public FrostFsRange Range { get; } = range; + + public bool Raw { get; } = raw; + + /// + public FrostFsSessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs b/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs new file mode 100644 index 0000000..661ed64 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs @@ -0,0 +1,20 @@ +namespace FrostFS.SDK.ClientV2; + +public sealed class PrmRangeHashGet( + FrostFsContainerId containerId, + FrostFsObjectId objectId, + FrostFsRange[] ranges, + byte[] salt, + CallContext? ctx = null) : PrmBase(ctx), ISessionToken +{ + public FrostFsContainerId ContainerId { get; } = containerId; + + public FrostFsObjectId ObjectId { get; } = objectId; + + public FrostFsRange[] Ranges { get; } = ranges; + + public byte[] Salt { get; } = salt; + + /// + public FrostFsSessionToken? SessionToken { get; set; } +} diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs b/src/FrostFS.SDK.ClientV2/Pool/ClientStatusMonitor.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/ClientStatusMonitor.cs rename to src/FrostFS.SDK.ClientV2/Pool/ClientStatusMonitor.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs b/src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs rename to src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs index ddde002..206f69e 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/ClientWrapper.cs +++ b/src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs @@ -101,9 +101,9 @@ public class ClientWrapper : ClientStatusMonitor await ScheduleGracefulClose().ConfigureAwait(false); } -#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually + //#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually FrostFSClient client = new(WrapperPrm, sessionCache); -#pragma warning restore CA2000 + //#pragma warning restore CA2000 //TODO: set additioanl params var error = await client.Dial(ctx).ConfigureAwait(false); diff --git a/src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs b/src/FrostFS.SDK.ClientV2/Pool/HealthyStatus.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/HealthyStatus.cs rename to src/FrostFS.SDK.ClientV2/Pool/HealthyStatus.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs b/src/FrostFS.SDK.ClientV2/Pool/IClientStatus.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/IClientStatus.cs rename to src/FrostFS.SDK.ClientV2/Pool/IClientStatus.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs b/src/FrostFS.SDK.ClientV2/Pool/InitParameters.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/InitParameters.cs rename to src/FrostFS.SDK.ClientV2/Pool/InitParameters.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs b/src/FrostFS.SDK.ClientV2/Pool/InnerPool.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/InnerPool.cs rename to src/FrostFS.SDK.ClientV2/Pool/InnerPool.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs b/src/FrostFS.SDK.ClientV2/Pool/MethodIndex.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/MethodIndex.cs rename to src/FrostFS.SDK.ClientV2/Pool/MethodIndex.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs b/src/FrostFS.SDK.ClientV2/Pool/MethodStatus.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/MethodStatus.cs rename to src/FrostFS.SDK.ClientV2/Pool/MethodStatus.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs b/src/FrostFS.SDK.ClientV2/Pool/NodeParam.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/NodeParam.cs rename to src/FrostFS.SDK.ClientV2/Pool/NodeParam.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs b/src/FrostFS.SDK.ClientV2/Pool/NodeStatistic.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/NodeStatistic.cs rename to src/FrostFS.SDK.ClientV2/Pool/NodeStatistic.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs b/src/FrostFS.SDK.ClientV2/Pool/NodesParam.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/NodesParam.cs rename to src/FrostFS.SDK.ClientV2/Pool/NodesParam.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs b/src/FrostFS.SDK.ClientV2/Pool/Pool.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Poll/Pool.cs rename to src/FrostFS.SDK.ClientV2/Pool/Pool.cs index 3e20634..9e34551 100644 --- a/src/FrostFS.SDK.ClientV2/Poll/Pool.cs +++ b/src/FrostFS.SDK.ClientV2/Pool/Pool.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -711,6 +710,62 @@ public partial class Pool : IFrostFSClient return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false); } + public async Task PatchObjectAsync(PrmObjectPatch args) + { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Connection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); + } + + public async Task GetRangeAsync(PrmRangeGet args) + { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Connection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetRangeAsync(args).ConfigureAwait(false); + } + + public async Task>> GetRangeHashAsync(PrmRangeHashGet args) + { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Connection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.GetRangeHashAsync(args).ConfigureAwait(false); + } + + public async Task PatchAsync(PrmObjectPatch args) + { + if (args is null) + { + throw new ArgumentNullException(nameof(args)); + } + + var client = Connection(); + + args.Context.PoolErrorHandler = client.HandleError; + + return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); + } + public async Task DeleteObjectAsync(PrmObjectDelete args) { if (args is null) diff --git a/src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs b/src/FrostFS.SDK.ClientV2/Pool/RebalanceParameters.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/RebalanceParameters.cs rename to src/FrostFS.SDK.ClientV2/Pool/RebalanceParameters.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs b/src/FrostFS.SDK.ClientV2/Pool/RequestInfo.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/RequestInfo.cs rename to src/FrostFS.SDK.ClientV2/Pool/RequestInfo.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/Sampler.cs b/src/FrostFS.SDK.ClientV2/Pool/Sampler.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/Sampler.cs rename to src/FrostFS.SDK.ClientV2/Pool/Sampler.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs b/src/FrostFS.SDK.ClientV2/Pool/SessionCache.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/SessionCache.cs rename to src/FrostFS.SDK.ClientV2/Pool/SessionCache.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/Statistic.cs b/src/FrostFS.SDK.ClientV2/Pool/Statistic.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/Statistic.cs rename to src/FrostFS.SDK.ClientV2/Pool/Statistic.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs b/src/FrostFS.SDK.ClientV2/Pool/StatusSnapshot.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/StatusSnapshot.cs rename to src/FrostFS.SDK.ClientV2/Pool/StatusSnapshot.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/WorkList.cs b/src/FrostFS.SDK.ClientV2/Pool/WorkList.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/WorkList.cs rename to src/FrostFS.SDK.ClientV2/Pool/WorkList.cs diff --git a/src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs b/src/FrostFS.SDK.ClientV2/Pool/WrapperPrm.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Poll/WrapperPrm.cs rename to src/FrostFS.SDK.ClientV2/Pool/WrapperPrm.cs diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index bcea7c7..bb742a5 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; @@ -107,6 +108,101 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return await GetObject(request, ctx).ConfigureAwait(false); } + internal async Task GetRangeAsync(PrmRangeGet args) + { + var ctx = args.Context!; + + ctx.Key ??= ClientContext.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new ArgumentNullException(nameof(args), "Key is null"); + + var request = new GetRangeRequest + { + Body = new GetRangeRequest.Types.Body + { + Address = new Address + { + ContainerId = args.ContainerId.ToMessage(), + ObjectId = args.ObjectId.ToMessage() + }, + Range = new Object.Range + { + Offset = args.Range.Offset, + Length = args.Range.Length + }, + Raw = args.Raw + } + }; + + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + + sessionToken.CreateObjectTokenContext( + request.Body.Address, + ObjectSessionContext.Types.Verb.Range, + ctx.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); + + request.Sign(ctx.Key); + + var call = client.GetRange(request, null, ctx.Deadline, ctx.CancellationToken); + return new RangeReader(call); + } + + internal async Task>> GetRangeHashAsync(PrmRangeHashGet args) + { + var ctx = args.Context!; + + ctx.Key ??= ClientContext.Key?.ECDsaKey; + + if (ctx.Key == null) + throw new ArgumentNullException(nameof(args), "Key is null"); + + var request = new GetRangeHashRequest + { + Body = new GetRangeHashRequest.Types.Body + { + Address = new Address + { + ContainerId = args.ContainerId.ToMessage(), + ObjectId = args.ObjectId.ToMessage() + }, + Type = ChecksumType.Sha256, + Salt = ByteString.CopyFrom(args.Salt) // TODO: create a type with calculated cashed ByteString inside + } + }; + + foreach (var range in args.Ranges) + { + request.Body.Ranges.Add(new Object.Range + { + Length = range.Length, + Offset = range.Offset + }); + } + + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + + sessionToken.CreateObjectTokenContext( + request.Body.Address, + ObjectSessionContext.Types.Verb.Rangehash, + ctx.Key); + + request.AddMetaHeader(args.XHeaders, sessionToken); + + request.Sign(ctx.Key); + + var response = await client.GetRangeHashAsync(request, null, ctx.Deadline, ctx.CancellationToken); + + Verifier.CheckResponse(response); + + var hashCollection = response.Body.HashList.ToArray().Select(h => h.Memory); + + return hashCollection; + } + + internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; @@ -191,7 +287,9 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl throw new ArgumentNullException(nameof(args), "Payload is null"); if (args.ClientCut) + { return await PutClientCutObject(args).ConfigureAwait(false); + } else { if (args.Header.PayloadLength > 0) @@ -199,7 +297,9 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl else if (args.Payload.CanSeek) args.FullLength = (ulong)args.Payload.Length; - return (await PutStreamObject(args).ConfigureAwait(false)).ObjectId; + var response = await PutStreamObject(args).ConfigureAwait(false); + + return response.ObjectId; } } @@ -235,6 +335,100 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); } + internal async Task PatchObjectAsync(PrmObjectPatch args) + { + var ctx = args.Context!; + if (ctx.Key == null) + throw new ArgumentNullException(nameof(args), "Key is null"); + + var chunkSize = args.MaxPayloadPatchChunkLength; + Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); + + var call = client.Patch(null, ctx.Deadline, ctx.CancellationToken); + + byte[]? chunkBuffer = null; + try + { + // common + chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + + var address = new Address + { + ObjectId = args.Address.ObjectId, + ContainerId = args.Address.ContainerId + }; + + var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + + sessionToken.CreateObjectTokenContext( + address, + ObjectSessionContext.Types.Verb.Patch, + ctx.Key + ); + + var request = new PatchRequest() + { + Body = new() + { + Address = address, + ReplaceAttributes = args.ReplaceAttributes, + } + }; + + bool isFirstChunk = true; + ulong currentPos = args.Range.Offset; + + while (true) + { + var bytesCount = await payload.ReadAsync(chunkBuffer, 0, chunkSize, ctx.CancellationToken).ConfigureAwait(false); + + if (bytesCount == 0) + { + break; + } + + if (isFirstChunk && args.NewAttributes != null && args.NewAttributes.Length > 0) + { + foreach (var attr in args.NewAttributes) + { + request.Body.NewAttributes.Add(attr.ToMessage()); + } + } + + request.Body.Patch = new PatchRequest.Types.Body.Types.Patch + { + Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount), + SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount } + }; + + currentPos += (ulong)bytesCount; + + request.AddMetaHeader(args.XHeaders, sessionToken); + + request.Sign(ctx.Key); + + await call.RequestStream.WriteAsync(request).ConfigureAwait(false); + + isFirstChunk = false; + } + } + finally + { + await call.RequestStream.CompleteAsync().ConfigureAwait(false); + + if (chunkBuffer != null) + { + ArrayPool.Shared.Return(chunkBuffer); + } + } + + var response = await call.ResponseAsync.ConfigureAwait(false); + + Verifier.CheckResponse(response); + + return response.Body.ObjectId.ToModel(); + } + private async Task PutClientCutObject(PrmObjectPut args) { var ctx = args.Context!; @@ -406,7 +600,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } } - private async Task GetUploadStream(PrmObjectPut args, CallContext ctx) + private async Task> GetUploadStream(PrmObjectPut args, CallContext ctx) { var header = args.Header!; @@ -451,6 +645,20 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return await PutObjectInit(initRequest, ctx).ConfigureAwait(false); } + private async Task> PutObjectInit(PutRequest initRequest, CallContext ctx) + { + if (initRequest is null) + { + throw new ArgumentNullException(nameof(initRequest)); + } + + var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); + + await call.RequestStream.WriteAsync(initRequest).ConfigureAwait(false); + + return new ObjectStreamer(call); + } + private async Task GetObject(GetRequest request, CallContext ctx) { var reader = GetObjectInit(request, ctx); @@ -473,20 +681,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return new ObjectReader(call); } - private async Task PutObjectInit(PutRequest initRequest, CallContext ctx) - { - if (initRequest is null) - { - throw new ArgumentNullException(nameof(initRequest)); - } - - var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); - - await call.RequestStream.WriteAsync(initRequest).ConfigureAwait(false); - - return new ObjectStreamer(call); - } - private async IAsyncEnumerable SearchObjects(SearchRequest request, CallContext ctx) { using var stream = GetSearchReader(request, ctx); diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs index 7df2343..c6af0c8 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs @@ -32,7 +32,7 @@ public sealed class ObjectReader(AsyncServerStreamingCall call) : I }; } - public async Task?> ReadChunk(CancellationToken cancellationToken = default) + public async ValueTask?> ReadChunk(CancellationToken cancellationToken = default) { if (!await Call.ResponseStream.MoveNext(cancellationToken).ConfigureAwait(false)) return null; diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs index acd0d28..a23c109 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs +++ b/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs @@ -1,17 +1,15 @@ using System; using System.Threading.Tasks; -using FrostFS.Object; - using Grpc.Core; namespace FrostFS.SDK.ClientV2; -internal sealed class ObjectStreamer(AsyncClientStreamingCall call) : IDisposable +internal sealed class ObjectStreamer(AsyncClientStreamingCall call) : IDisposable { - public AsyncClientStreamingCall Call { get; private set; } = call; + public AsyncClientStreamingCall Call { get; private set; } = call; - public async Task Write(PutRequest request) + public async Task Write(TRequest request) { if (request is null) { @@ -21,7 +19,7 @@ internal sealed class ObjectStreamer(AsyncClientStreamingCall Close() + public async Task Close() { await Call.RequestStream.CompleteAsync().ConfigureAwait(false); diff --git a/src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs b/src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs new file mode 100644 index 0000000..d1128c8 --- /dev/null +++ b/src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs @@ -0,0 +1,38 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using FrostFS.Object; + +using Grpc.Core; + +namespace FrostFS.SDK.ClientV2; + +public sealed class RangeReader(AsyncServerStreamingCall call) : IObjectReader +{ + private bool disposed; + + public AsyncServerStreamingCall Call { get; private set; } = call; + + public async ValueTask?> ReadChunk(CancellationToken cancellationToken = default) + { + if (!await Call.ResponseStream.MoveNext(cancellationToken).ConfigureAwait(false)) + return null; + + var response = Call.ResponseStream.Current; + Verifier.CheckResponse(response); + + return response.Body.Chunk.Memory; + } + + public void Dispose() + { + if (!disposed) + { + Call?.Dispose(); + GC.SuppressFinalize(this); + + disposed = true; + } + } +} diff --git a/src/FrostFS.SDK.ProtosV2/accounting/service.proto b/src/FrostFS.SDK.ProtosV2/accounting/service.proto index 715ef63..6049e0f 100644 --- a/src/FrostFS.SDK.ProtosV2/accounting/service.proto +++ b/src/FrostFS.SDK.ProtosV2/accounting/service.proto @@ -9,13 +9,13 @@ import "accounting/types.proto"; import "refs/types.proto"; import "session/types.proto"; -// Accounting service provides methods for interaction with NeoFS sidechain via -// other NeoFS nodes to get information about the account balance. Deposit and -// Withdraw operations can't be implemented here, as they require Mainnet NeoFS -// smart contract invocation. Transfer operations between internal NeoFS -// accounts are possible if both use the same token type. +// Accounting service provides methods for interaction with FrostFS sidechain +// via other FrostFS nodes to get information about the account balance. Deposit +// and Withdraw operations can't be implemented here, as they require Mainnet +// FrostFS smart contract invocation. Transfer operations between internal +// FrostFS accounts are possible if both use the same token type. service AccountingService { - // Returns the amount of funds in GAS token for the requested NeoFS account. + // Returns the amount of funds in GAS token for the requested FrostFS account. // // Statuses: // - **OK** (0, SECTION_SUCCESS): @@ -27,9 +27,9 @@ service AccountingService { // BalanceRequest message message BalanceRequest { // To indicate the account for which the balance is requested, its identifier - // is used. It can be any existing account in NeoFS sidechain `Balance` smart - // contract. If omitted, client implementation MUST set it to the request's - // signer `OwnerID`. + // is used. It can be any existing account in FrostFS sidechain `Balance` + // smart contract. If omitted, client implementation MUST set it to the + // request's signer `OwnerID`. message Body { // Valid user identifier in `OwnerID` format for which the balance is // requested. Required field. diff --git a/src/FrostFS.SDK.ProtosV2/acl/types.proto b/src/FrostFS.SDK.ProtosV2/acl/types.proto index 186f08f..a1d9ae2 100644 --- a/src/FrostFS.SDK.ProtosV2/acl/types.proto +++ b/src/FrostFS.SDK.ProtosV2/acl/types.proto @@ -6,6 +6,7 @@ option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl/grpc;ac option csharp_namespace = "FrostFS.Acl"; import "refs/types.proto"; +import "ape/types.proto"; // Target role of the access control rule in access control list. enum Role { @@ -88,14 +89,14 @@ enum HeaderType { // Filter object headers OBJECT = 2; - // Filter service headers. These are not processed by NeoFS nodes and + // Filter service headers. These are not processed by FrostFS nodes and // exist for service use only. SERVICE = 3; } // Describes a single eACL rule. message EACLRecord { - // NeoFS request Verb to match + // FrostFS request Verb to match Operation operation = 1 [ json_name = "operation" ]; // Rule execution result. Either allows or denies access if filters match. @@ -164,7 +165,7 @@ message EACLRecord { // Extended ACL rules table. A list of ACL rules defined additionally to Basic // ACL. Extended ACL rules can be attached to a container and can be updated // or may be defined in `BearerToken` structure. Please see the corresponding -// NeoFS Technical Specification section for detailed description. +// FrostFS Technical Specification section for detailed description. message EACLTable { // eACL format version. Effectively, the version of API library used to create // eACL Table. @@ -194,6 +195,9 @@ message BearerToken { // container. If it contains `container_id` field, bearer token is only // valid for this specific container. Otherwise, any container of the same // owner is allowed. + // + // Deprecated: eACL tables are no longer relevant - `APEOverrides` should be + // used instead. EACLTable eacl_table = 1 [ json_name = "eaclTable" ]; // `OwnerID` defines to whom the token was issued. It must match the request @@ -218,6 +222,24 @@ message BearerToken { // AllowImpersonate flag to consider token signer as request owner. // If this field is true extended ACL table in token body isn't processed. bool allow_impersonate = 4 [ json_name = "allowImpersonate" ]; + + // APEOverride is the list of APE chains defined for a target. + // These chains are meant to serve as overrides to the already defined (or + // even undefined) APE chains for the target (see contract `Policy`). + // + // The server-side processing of the bearer token with set APE overrides + // must verify if a client is permitted to override chains for the target, + // preventing unauthorized access through the APE mechanism. + message APEOverride { + // Target for which chains are applied. + frostfs.v2.ape.ChainTarget target = 1 [ json_name = "target" ]; + + // The list of APE chains. + repeated frostfs.v2.ape.Chain chains = 2 [ json_name = "chains" ]; + } + + // APE override for the target. + APEOverride ape_override = 5 [ json_name = "apeOverride" ]; } // Bearer Token body Body body = 1 [ json_name = "body" ]; diff --git a/src/FrostFS.SDK.ProtosV2/netmap/service.proto b/src/FrostFS.SDK.ProtosV2/netmap/service.proto index 8611d9a..11f8f96 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/service.proto +++ b/src/FrostFS.SDK.ProtosV2/netmap/service.proto @@ -12,7 +12,7 @@ import "session/types.proto"; // `NetmapService` provides methods to work with `Network Map` and the // information required to build it. The resulting `Network Map` is stored in // sidechain `Netmap` smart contract, while related information can be obtained -// from other NeoFS nodes. +// from other FrostFS nodes. service NetmapService { // Get NodeInfo structure from the particular node directly. // Node information can be taken from `Netmap` smart contract. In some cases, @@ -27,7 +27,7 @@ service NetmapService { // - Common failures (SECTION_FAILURE_COMMON). rpc LocalNodeInfo(LocalNodeInfoRequest) returns (LocalNodeInfoResponse); - // Read recent information about the NeoFS network. + // Read recent information about the FrostFS network. // // Statuses: // - **OK** (0, SECTION_SUCCESS): @@ -35,7 +35,7 @@ service NetmapService { // - Common failures (SECTION_FAILURE_COMMON). rpc NetworkInfo(NetworkInfoRequest) returns (NetworkInfoResponse); - // Returns network map snapshot of the current NeoFS epoch. + // Returns network map snapshot of the current FrostFS epoch. // // Statuses: // - **OK** (0, SECTION_SUCCESS): @@ -65,7 +65,7 @@ message LocalNodeInfoRequest { message LocalNodeInfoResponse { // Local Node Info, including API Version in use. message Body { - // Latest NeoFS API version in use + // Latest FrostFS API version in use neo.fs.v2.refs.Version version = 1; // NodeInfo structure with recent information from node itself diff --git a/src/FrostFS.SDK.ProtosV2/netmap/types.proto b/src/FrostFS.SDK.ProtosV2/netmap/types.proto index baaca04..5f0e93e 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/types.proto +++ b/src/FrostFS.SDK.ProtosV2/netmap/types.proto @@ -36,6 +36,9 @@ enum Operation { // Logical negation NOT = 9; + + // Matches pattern + LIKE = 10; } // Selector modifier shows how the node set will be formed. By default selector @@ -119,7 +122,7 @@ message PlacementPolicy { // bucket repeated Replica replicas = 1 [ json_name = "replicas" ]; - // Container backup factor controls how deep NeoFS will search for nodes + // Container backup factor controls how deep FrostFS will search for nodes // alternatives to include into container's nodes subset uint32 container_backup_factor = 2 [ json_name = "containerBackupFactor" ]; @@ -133,25 +136,25 @@ message PlacementPolicy { bool unique = 5 [ json_name = "unique" ]; } -// NeoFS node description +// FrostFS node description message NodeInfo { - // Public key of the NeoFS node in a binary format + // Public key of the FrostFS node in a binary format bytes public_key = 1 [ json_name = "publicKey" ]; // Ways to connect to a node repeated string addresses = 2 [ json_name = "addresses" ]; - // Administrator-defined Attributes of the NeoFS Storage Node. + // Administrator-defined Attributes of the FrostFS Storage Node. // // `Attribute` is a Key-Value metadata pair. Key name must be a valid UTF-8 // string. Value can't be empty. // // Attributes can be constructed into a chain of attributes: any attribute can // have a parent attribute and a child attribute (except the first and the - // last one). A string representation of the chain of attributes in NeoFS + // last one). A string representation of the chain of attributes in FrostFS // Storage Node configuration uses ":" and "/" symbols, e.g.: // - // `NEOFS_NODE_ATTRIBUTE_1=key1:val1/key2:val2` + // `FrostFS_NODE_ATTRIBUTE_1=key1:val1/key2:val2` // // Therefore the string attribute representation in the Node configuration // must use "\:", "\/" and "\\" escaped symbols if any of them appears in an @@ -198,8 +201,8 @@ message NodeInfo { // [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2). Calculated // automatically from `UN-LOCODE` attribute. // * Continent \ - // Node's continent name according to the [Seven-Continent model] - // (https://en.wikipedia.org/wiki/Continent#Number). Calculated + // Node's continent name according to the [Seven-Continent + // model](https://en.wikipedia.org/wiki/Continent#Number). Calculated // automatically from `UN-LOCODE` attribute. // * ExternalAddr // Node's preferred way for communications with external clients. @@ -207,7 +210,7 @@ message NodeInfo { // Must contain a comma-separated list of multi-addresses. // // For detailed description of each well-known attribute please see the - // corresponding section in NeoFS Technical Specification. + // corresponding section in FrostFS Technical Specification. message Attribute { // Key of the node attribute string key = 1 [ json_name = "key" ]; @@ -219,13 +222,13 @@ message NodeInfo { // `Country`. repeated string parents = 3 [ json_name = "parents" ]; } - // Carries list of the NeoFS node attributes in a key-value form. Key name + // Carries list of the FrostFS node attributes in a key-value form. Key name // must be a node-unique valid UTF-8 string. Value can't be empty. NodeInfo // structures with duplicated attribute names or attributes with empty values // will be considered invalid. repeated Attribute attributes = 3 [ json_name = "attributes" ]; - // Represents the enumeration of various states of the NeoFS node. + // Represents the enumeration of various states of the FrostFS node. enum State { // Unknown state UNSPECIFIED = 0; @@ -240,7 +243,7 @@ message NodeInfo { MAINTENANCE = 3; } - // Carries state of the NeoFS node + // Carries state of the FrostFS node State state = 4 [ json_name = "state" ]; } @@ -253,7 +256,7 @@ message Netmap { repeated NodeInfo nodes = 2 [ json_name = "nodes" ]; } -// NeoFS network configuration +// FrostFS network configuration message NetworkConfig { // Single configuration parameter. Key MUST be network-unique. // @@ -272,7 +275,7 @@ message NetworkConfig { // Fee paid for container creation by the container owner. // Value: little-endian integer. Default: 0. // - **EpochDuration** \ - // NeoFS epoch duration measured in Sidechain blocks. + // FrostFS epoch duration measured in Sidechain blocks. // Value: little-endian integer. Default: 0. // - **HomomorphicHashingDisabled** \ // Flag of disabling the homomorphic hashing of objects' payload. @@ -284,8 +287,39 @@ message NetworkConfig { // Flag allowing setting the MAINTENANCE state to storage nodes. // Value: true if any byte != 0. Default: false. // - **MaxObjectSize** \ - // Maximum size of physically stored NeoFS object measured in bytes. + // Maximum size of physically stored FrostFS object measured in bytes. // Value: little-endian integer. Default: 0. + // + // This value refers to the maximum size of a **physically** stored object + // in FrostFS. However, from a user's perspective, the **logical** size of a + // stored object can be significantly larger. The relationship between the + // physical and logical object sizes is governed by the following formula + // + // ```math + // \mathrm{Stored\ Object\ Size} \le + // \frac{ + // \left(\mathrm{Max\ Object\ Size}\right)^2 + // }{ + // \mathrm{Object\ ID\ Size} + // } + // ``` + // + // This arises from the fact that a tombstone, also being an object, stores + // the IDs of inhumed objects and cannot be divided into smaller objects, + // thus having an upper limit for its size. + // + // For example, if: + // * Max Object Size Size = 64 MiB; + // * Object ID Size = 32 B; + // + // then: + // ```math + // \mathrm{Stored\ Object\ Size} \le + // \frac{\left(64\ \mathrm{MiB}\right)^2}{32\ \mathrm{B}} = + // \frac{2^{52}}{2^5}\ \mathrm{B} = + // 2^{47}\ \mathrm{B} = + // 128\ \mathrm{TiB} + // ``` // - **WithdrawFee** \ // Fee paid for withdrawal of funds paid by the account owner. // Value: little-endian integer. Default: 0. @@ -306,18 +340,18 @@ message NetworkConfig { repeated Parameter parameters = 1 [ json_name = "parameters" ]; } -// Information about NeoFS network +// Information about FrostFS network message NetworkInfo { - // Number of the current epoch in the NeoFS network + // Number of the current epoch in the FrostFS network uint64 current_epoch = 1 [ json_name = "currentEpoch" ]; - // Magic number of the sidechain of the NeoFS network + // Magic number of the sidechain of the FrostFS network uint64 magic_number = 2 [ json_name = "magicNumber" ]; - // MillisecondsPerBlock network parameter of the sidechain of the NeoFS + // MillisecondsPerBlock network parameter of the sidechain of the FrostFS // network int64 ms_per_block = 3 [ json_name = "msPerBlock" ]; - // NeoFS network configuration + // FrostFS network configuration NetworkConfig network_config = 4 [ json_name = "networkConfig" ]; } diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs index 7c0c87d..6691400 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs @@ -458,4 +458,60 @@ namespace FrostFS.Object return Body; } } + + public partial class PatchRequest : IRequest + { + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (RequestMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (RequestVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } + + public partial class PatchResponse : IResponse + { + IMetaHeader IVerifiableMessage.GetMetaHeader() + { + return MetaHeader; + } + + IVerificationHeader IVerifiableMessage.GetVerificationHeader() + { + return VerifyHeader; + } + + void IVerifiableMessage.SetMetaHeader(IMetaHeader metaHeader) + { + MetaHeader = (ResponseMetaHeader)metaHeader; + } + + void IVerifiableMessage.SetVerificationHeader(IVerificationHeader verificationHeader) + { + VerifyHeader = (ResponseVerificationHeader)verificationHeader; + } + + public IMessage GetBody() + { + return Body; + } + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.ProtosV2/object/service.proto b/src/FrostFS.SDK.ProtosV2/object/service.proto index 383e83b..2b8042b 100644 --- a/src/FrostFS.SDK.ProtosV2/object/service.proto +++ b/src/FrostFS.SDK.ProtosV2/object/service.proto @@ -151,7 +151,7 @@ service ObjectService { rpc Head(HeadRequest) returns (HeadResponse); // Search objects in container. Search query allows to match by Object - // Header's filed values. Please see the corresponding NeoFS Technical + // Header's filed values. Please see the corresponding FrostFS Technical // Specification section for more details. // // Extended headers can change `Search` behaviour: @@ -283,6 +283,55 @@ service ObjectService { // - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \ // provided session token has expired. rpc PutSingle(PutSingleRequest) returns (PutSingleResponse); + + // Patch the object. Request uses gRPC stream. First message must set + // the address of the object that is going to get patched. If the object's + // attributes are patched, then these attrubutes must be set only within the + // first stream message. + // + // If the patch request is performed by NOT the object's owner but if the + // actor has the permission to perform the patch, then `OwnerID` of the object + // is changed. In this case the object's owner loses the object's ownership + // after the patch request is successfully done. + // + // As objects are content-addressable the patching causes new object ID + // generation for the patched object. This object id is set witihn + // `PatchResponse`. But the object id may remain unchanged in such cases: + // 1. The chunk of the applying patch contains the same value as the object's + // payload within the same range; + // 2. The patch that reverts the changes applied by preceding patch; + // 3. The application of the same patches for the object a few times. + // + // Extended headers can change `Patch` behaviour: + // * [ __SYSTEM__NETMAP_EPOCH \ + // (`__NEOFS__NETMAP_EPOCH` is deprecated) \ + // Will use the requsted version of Network Map for object placement + // calculation. + // + // Please refer to detailed `XHeader` description. + // + // Statuses: + // - **OK** (0, SECTION_SUCCESS): \ + // object has been successfully patched and saved in the container; + // - Common failures (SECTION_FAILURE_COMMON); + // - **ACCESS_DENIED** (2048, SECTION_OBJECT): \ + // write access to the container is denied; + // - **OBJECT_NOT_FOUND** (2049, SECTION_OBJECT): \ + // object not found in container; + // - **OBJECT_ALREADY_REMOVED** (2052, SECTION_OBJECT): \ + // the requested object has been marked as deleted. + // - **OUT_OF_RANGE** (2053, SECTION_OBJECT): \ + // the requested range is out of bounds; + // - **CONTAINER_NOT_FOUND** (3072, SECTION_CONTAINER): \ + // object storage container not found; + // - **CONTAINER_ACCESS_DENIED** (3074, SECTION_CONTAINER): \ + // access to container is denied; + // - **TOKEN_NOT_FOUND** (4096, SECTION_SESSION): \ + // (for trusted object preparation) session private key does not exist or + // has been deleted; + // - **TOKEN_EXPIRED** (4097, SECTION_SESSION): \ + // provided session token has expired. + rpc Patch(stream PatchRequest) returns (PatchResponse); } // GET object request @@ -583,6 +632,9 @@ message SearchRequest { // object_id of parent // * $Object:split.splitID \ // 16 byte UUIDv4 used to identify the split object hierarchy parts + // * $Object:ec.parent \ + // If the object is stored according to EC policy, then ec_parent + // attribute is set to return an id list of all related EC chunks. // // There are some well-known filter aliases to match objects by certain // properties: @@ -813,4 +865,75 @@ message PutSingleResponse { // authenticate the nodes of the message route and check the correctness of // transmission. neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; -} \ No newline at end of file +} + +// Object PATCH request +message PatchRequest { + // PATCH request body + message Body { + // The address of the object that is requested to get patched. + neo.fs.v2.refs.Address address = 1; + + // New attributes for the object. See `replace_attributes` flag usage to + // define how new attributes should be set. + repeated neo.fs.v2.object.Header.Attribute new_attributes = 2; + + // If this flag is set, then the object's attributes will be entirely + // replaced by `new_attributes` list. The empty `new_attributes` list with + // `replace_attributes = true` just resets attributes list for the object. + // + // Default `false` value for this flag means the attributes will be just + // merged. If the incoming `new_attributes` list contains already existing + // key, then it just replaces it while merging the lists. + bool replace_attributes = 3; + + // The patch for the object's payload. + message Patch { + // The range of the source object for which the payload is replaced by the + // patch's chunk. If the range's `length = 0`, then the patch's chunk is + // just appended to the original payload starting from the `offest` + // without any replace. + Range source_range = 1; + + // The chunk that is being appended to or that replaces the original + // payload on the given range. + bytes chunk = 2; + } + + // The patch that is applied for the object. + Patch patch = 4; + } + + // Body for patch request message. + Body body = 1; + + // Carries request meta information. Header data is used only to regulate + // message transport and does not affect request execution. + neo.fs.v2.session.RequestMetaHeader meta_header = 2; + + // Carries request verification information. This header is used to + // authenticate the nodes of the message route and check the correctness of + // transmission. + neo.fs.v2.session.RequestVerificationHeader verify_header = 3; +} + +// Object PATCH response +message PatchResponse { + // PATCH response body + message Body { + // The object ID of the saved patched object. + neo.fs.v2.refs.ObjectID object_id = 1; + } + + // Body for patch response message. + Body body = 1; + + // Carries response meta information. Header data is used only to regulate + // message transport and does not affect request execution. + neo.fs.v2.session.ResponseMetaHeader meta_header = 2; + + // Carries response verification information. This header is used to + // authenticate the nodes of the message route and check the correctness of + // transmission. + neo.fs.v2.session.ResponseVerificationHeader verify_header = 3; +} diff --git a/src/FrostFS.SDK.ProtosV2/object/types.proto b/src/FrostFS.SDK.ProtosV2/object/types.proto index 6e62b86..b838c8e 100644 --- a/src/FrostFS.SDK.ProtosV2/object/types.proto +++ b/src/FrostFS.SDK.ProtosV2/object/types.proto @@ -155,7 +155,7 @@ message Header { // MIME Content Type of object's payload // // For detailed description of each well-known attribute please see the - // corresponding section in NeoFS Technical Specification. + // corresponding section in FrostFS Technical Specification. message Attribute { // string key to the object attribute string key = 1 [ json_name = "key" ]; @@ -208,6 +208,18 @@ message Header { uint32 header_length = 4 [ json_name = "headerLength" ]; // Chunk of a parent header. bytes header = 5 [ json_name = "header" ]; + // As the origin object is EC-splitted its identifier is known to all + // chunks as parent. But parent itself can be a part of Split (does not + // relate to EC-split). In this case parent_split_id should be set. + bytes parent_split_id = 6 [ json_name = "parentSplitID" ]; + // EC-parent's parent ID. parent_split_parent_id is set if EC-parent, + // itself, is a part of Split and if an object ID of its parent is + // presented. The field allows to determine how EC-chunk is placed in Split + // hierarchy. + neo.fs.v2.refs.ObjectID parent_split_parent_id = 7 + [ json_name = "parentSplitParentID" ]; + // EC parent's attributes. + repeated Attribute parent_attributes = 8 [ json_name = "parentAttributes" ]; } // Erasure code chunk information. EC ec = 12 [ json_name = "ec" ]; diff --git a/src/FrostFS.SDK.ProtosV2/refs/types.proto b/src/FrostFS.SDK.ProtosV2/refs/types.proto index 15d32c1..014c736 100644 --- a/src/FrostFS.SDK.ProtosV2/refs/types.proto +++ b/src/FrostFS.SDK.ProtosV2/refs/types.proto @@ -5,7 +5,7 @@ package neo.fs.v2.refs; option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs/grpc;refs"; option csharp_namespace = "FrostFS.Refs"; -// Objects in NeoFS are addressed by their ContainerID and ObjectID. +// Objects in FrostFS are addressed by their ContainerID and ObjectID. // // String presentation of `Address` is a concatenation of string encoded // `ContainerID` and `ObjectID` delimited by '/' character. @@ -16,8 +16,9 @@ message Address { ObjectID object_id = 2 [ json_name = "objectID" ]; } -// NeoFS Object unique identifier. Objects are immutable and content-addressed. -// It means `ObjectID` will change if the `header` or the `payload` changes. +// FrostFS Object unique identifier. Objects are immutable and +// content-addressed. It means `ObjectID` will change if the `header` or the +// `payload` changes. // // `ObjectID` is a 32 byte long // [SHA256](https://csrc.nist.gov/publications/detail/fips/180/4/final) hash of @@ -37,7 +38,7 @@ message ObjectID { bytes value = 1 [ json_name = "value" ]; } -// NeoFS container identifier. Container structures are immutable and +// FrostFS container identifier. Container structures are immutable and // content-addressed. // // `ContainerID` is a 32 byte long @@ -90,7 +91,7 @@ message Version { uint32 minor = 2 [ json_name = "minor" ]; } -// Signature of something in NeoFS. +// Signature of something in FrostFS. message Signature { // Public key used for signing bytes key = 1 [ json_name = "key" ]; diff --git a/src/FrostFS.SDK.ProtosV2/session/service.proto b/src/FrostFS.SDK.ProtosV2/session/service.proto index 6f48e3a..6511f3b 100644 --- a/src/FrostFS.SDK.ProtosV2/session/service.proto +++ b/src/FrostFS.SDK.ProtosV2/session/service.proto @@ -11,7 +11,7 @@ import "session/types.proto"; // `SessionService` allows to establish a temporary trust relationship between // two peer nodes and generate a `SessionToken` as the proof of trust to be // attached in requests for further verification. Please see corresponding -// section of NeoFS Technical Specification for details. +// section of FrostFS Technical Specification for details. service SessionService { // Open a new session between two peers. // diff --git a/src/FrostFS.SDK.ProtosV2/session/types.proto b/src/FrostFS.SDK.ProtosV2/session/types.proto index d1a9ef1..bc0d7f1 100644 --- a/src/FrostFS.SDK.ProtosV2/session/types.proto +++ b/src/FrostFS.SDK.ProtosV2/session/types.proto @@ -36,6 +36,9 @@ message ObjectSessionContext { // Refers to object.GetRangeHash RPC call RANGEHASH = 7; + + // Refers to object.Patch RPC call + PATCH = 8; } // Type of request for which the token is issued Verb verb = 1 [ json_name = "verb" ]; @@ -47,7 +50,7 @@ message ObjectSessionContext { refs.ContainerID container = 1 [ json_name = "container" ]; // Indicates which objects the session is spread to. Objects are expected - // to be stored in the NeoFS container referenced by `container` field. + // to be stored in the FrostFS container referenced by `container` field. // Each element MUST have correct format. repeated refs.ObjectID objects = 2 [ json_name = "objects" ]; } @@ -85,7 +88,7 @@ message ContainerSessionContext { refs.ContainerID container_id = 3 [ json_name = "containerID" ]; } -// NeoFS Session Token. +// FrostFS Session Token. message SessionToken { // Session Token body message Body { @@ -123,7 +126,7 @@ message SessionToken { } // Session Token contains the proof of trust between peers to be attached in // requests for further verification. Please see corresponding section of - // NeoFS Technical Specification for details. + // FrostFS Technical Specification for details. Body body = 1 [ json_name = "body" ]; // Signature of `SessionToken` information @@ -183,7 +186,7 @@ message RequestMetaHeader { // `RequestMetaHeader` of the origin request RequestMetaHeader origin = 7 [ json_name = "origin" ]; - // NeoFS network magic. Must match the value for the network + // FrostFS network magic. Must match the value for the network // that the server belongs to. uint64 magic_number = 8 [ json_name = "magicNumber" ]; } diff --git a/src/FrostFS.SDK.ProtosV2/status/types.proto b/src/FrostFS.SDK.ProtosV2/status/types.proto index 8ab2f40..694f969 100644 --- a/src/FrostFS.SDK.ProtosV2/status/types.proto +++ b/src/FrostFS.SDK.ProtosV2/status/types.proto @@ -5,12 +5,12 @@ package neo.fs.v2.status; option go_package = "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status/grpc;status"; option csharp_namespace = "FrostFS.Status"; -// Declares the general format of the status returns of the NeoFS RPC protocol. -// Status is present in all response messages. Each RPC of NeoFS protocol -// describes the possible outcomes and details of the operation. +// Declares the general format of the status returns of the FrostFS RPC +// protocol. Status is present in all response messages. Each RPC of FrostFS +// protocol describes the possible outcomes and details of the operation. // // Each status is assigned a one-to-one numeric code. Any unique result of an -// operation in NeoFS is unambiguously associated with the code value. +// operation in FrostFS is unambiguously associated with the code value. // // Numerical set of codes is split into 1024-element sections. An enumeration // is defined for each section. Values can be referred to in the following ways: @@ -78,7 +78,7 @@ enum Section { SECTION_APE_MANAGER = 5; } -// Section of NeoFS successful return codes. +// Section of FrostFS successful return codes. enum Success { // [**0**] Default success. Not detailed. // If the server cannot match successful outcome to the code, it should @@ -93,9 +93,9 @@ enum CommonFail { // use this code. INTERNAL = 0; - // [**1025**] Wrong magic of the NeoFS network. + // [**1025**] Wrong magic of the FrostFS network. // Details: - // - [**0**] Magic number of the served NeoFS network (big-endian 64-bit + // - [**0**] Magic number of the served FrostFS network (big-endian 64-bit // unsigned integer). WRONG_MAGIC_NUMBER = 1; @@ -104,6 +104,11 @@ enum CommonFail { // [**1027**] Node is under maintenance. NODE_UNDER_MAINTENANCE = 3; + + // [**1028**] Invalid argument error. If the server fails on validation of a + // request parameter as the client sent it incorrectly, then this code should + // be used. + INVALID_ARGUMENT = 4; } // Section of statuses for object-related operations. diff --git a/src/FrostFS.SDK.ProtosV2/tombstone/types.proto b/src/FrostFS.SDK.ProtosV2/tombstone/types.proto index 739bef4..8780317 100644 --- a/src/FrostFS.SDK.ProtosV2/tombstone/types.proto +++ b/src/FrostFS.SDK.ProtosV2/tombstone/types.proto @@ -8,10 +8,10 @@ option csharp_namespace = "FrostFS.Tombstone"; import "refs/types.proto"; // Tombstone keeps record of deleted objects for a few epochs until they are -// purged from the NeoFS network. +// purged from the FrostFS network. message Tombstone { - // Last NeoFS epoch number of the tombstone lifetime. It's set by the - // tombstone creator depending on the current NeoFS network settings. A + // Last FrostFS epoch number of the tombstone lifetime. It's set by the + // tombstone creator depending on the current FrostFS network settings. A // tombstone object must have the same expiration epoch value in // `__SYSTEM__EXPIRATION_EPOCH` (`__NEOFS__EXPIRATION_EPOCH` is deprecated) // attribute. Otherwise, the tombstone will be rejected by a storage node. diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs new file mode 100644 index 0000000..e60b600 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography; + +using FrostFS.Object; +using FrostFS.SDK.ClientV2; +using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Cryptography; +using FrostFS.Session; + +using Google.Protobuf; + +using Grpc.Core; + +namespace FrostFS.SDK.Tests; + +public class AsyncStreamRangeReaderMock(string key, byte[] response) : ServiceBase(key), IAsyncStreamReader +{ + private readonly byte[] _response = response; + + public GetRangeResponse Current + { + get + { + var response = new GetRangeResponse + { + Body = new GetRangeResponse.Types.Body + { + Chunk = ByteString.CopyFrom(_response) + }, + MetaHeader = new ResponseMetaHeader() + }; + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return response; + } + } + + public Task MoveNext(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } +} + diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index d0b900d..7c44fc8 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -5,6 +5,7 @@ using FrostFS.Object; using FrostFS.SDK.ClientV2; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; +using FrostFS.Session; using Google.Protobuf; @@ -16,6 +17,32 @@ namespace FrostFS.SDK.Tests; public class ObjectMocker(string key) : ObjectServiceBase(key) { + public FrostFsObjectId? ObjectId { get; set; } + + public FrostFsObjectHeader? ObjectHeader { get; set; } + + public Header? HeadResponse { get; set; } + + public Collection? ResultObjectIds { get; } = []; + + public ClientStreamWriter? ClientStreamWriter { get; } = new(); + + public PatchStreamWriter? PatchStreamWriter { get; } = new(); + + public Collection PutSingleRequests { get; } = []; + + public Collection DeleteRequests { get; } = []; + + public Collection HeadRequests { get; } = []; + + public byte[] RangeResponse { get; set; } = []; + + public GetRangeRequest? GetRangeRequest { get; set; } + + public GetRangeHashRequest? GetRangeHashRequest { get; set; } + + public Collection RangeHashResponses { get; } = []; + public override Mock GetMock() { var mock = new Mock(); @@ -189,23 +216,88 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) }); } + mock.Setup(x => x.GetRange( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRangeRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + GetRangeRequest = r; + + return new AsyncServerStreamingCall( + new AsyncStreamRangeReaderMock(StringKey, RangeResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + mock.Setup(x => x.GetRangeHashAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((GetRangeHashRequest r, Metadata m, DateTime? dt, CancellationToken ct) => + { + Verifier.CheckRequest(r); + + GetRangeHashRequest = r; + + var response = new GetRangeHashResponse + { + Body = new GetRangeHashResponse.Types.Body(), + MetaHeader = ResponseMetaHeader + }; + + if (RangeHashResponses != null) + { + foreach (var hash in RangeHashResponses) + { + response.Body.HashList.Add(hash); + } + } + + response.VerifyHeader = GetResponseVerificationHeader(response); + + return new AsyncUnaryCall( + Task.FromResult(response), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + + + mock.Setup(x => x.Patch( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((Metadata m, DateTime? dt, CancellationToken ct) => + { + var patchResponse = new PatchResponse + { + Body = new PatchResponse.Types.Body + { + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData([1,2,3])) }, + }, + MetaHeader = ResponseMetaHeader + }; + + patchResponse.VerifyHeader = GetResponseVerificationHeader(patchResponse); + + return new AsyncClientStreamingCall( + PatchStreamWriter!, + Task.FromResult(patchResponse), + Task.FromResult(ResponseMetaData), + () => new Grpc.Core.Status(StatusCode.OK, string.Empty), + () => ResponseMetaData, + () => { }); + }); + return mock; } - - public FrostFsObjectId? ObjectId { get; set; } - - public FrostFsObjectHeader? ObjectHeader { get; set; } - - public Header? HeadResponse { get; set; } - - public Collection? ResultObjectIds { get; } = []; - - public ClientStreamWriter? ClientStreamWriter { get; private set; } = new(); - - public Collection PutSingleRequests { get; private set; } = []; - - public Collection DeleteRequests { get; private set; } = []; - - public Collection HeadRequests { get; private set; } = []; } diff --git a/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs new file mode 100644 index 0000000..a2672c8 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs @@ -0,0 +1,36 @@ +using System.Collections.ObjectModel; + +using FrostFS.SDK.ProtosV2.Interfaces; + +using Grpc.Core; + +namespace FrostFS.SDK.Tests; + +public class PatchStreamWriter : IClientStreamWriter +{ + private WriteOptions? _options; + + public Collection Messages { get; } = []; + + public bool CompletedTask { get; private set; } + + public WriteOptions? WriteOptions + { + get => _options; + set => _options = value; + } + + public Task CompleteAsync() + { + CompletedTask = true; + return Task.CompletedTask; + } + + public Task WriteAsync(IRequest message) + { + Object.PatchRequest pr = new((Object.PatchRequest)message); + Messages.Add(pr); + return Task.CompletedTask; + } +} + diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 85b42ca..3f3a404 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -10,6 +10,8 @@ using FrostFS.SDK.Cryptography; using Google.Protobuf; +using static FrostFS.Object.ECInfo.Types; + namespace FrostFS.SDK.Tests; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] @@ -223,4 +225,121 @@ public class ObjectTest : ObjectTestsBase Assert.Null(response.Split); } + + [Fact] + public async void GetRangeTest() + { + Mocker.ResultObjectIds!.Add(SHA256.HashData([])); + + Random rnd = new(); + var bytes = new byte[1024]; + rnd.NextBytes(bytes); + + Mocker.RangeResponse = bytes; + + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + var param = new PrmRangeGet(ContainerId, Mocker.ObjectId, new FrostFsRange(100, (ulong)Mocker.RangeResponse.Length)); + + var result = await GetClient().GetRangeAsync(param); + + Assert.NotNull(Mocker.GetRangeRequest); + + Assert.Equal(param.Range.Offset, Mocker.GetRangeRequest.Body.Range.Offset); + Assert.Equal(param.Range.Length, Mocker.GetRangeRequest.Body.Range.Length); + + Assert.NotNull(result); + + var chunk = await result.ReadChunk(); + + var chunkBytes = chunk.Value.Span.ToArray(); + + Assert.Equal(chunkBytes.Length, Mocker.RangeResponse.Length); + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(Mocker.RangeResponse)); + } + + [Fact] + public async void GetRangeHashTest() + { + Mocker.ResultObjectIds!.Add(SHA256.HashData([])); + + Random rnd = new(); + var bytes = new byte[1024]; + rnd.NextBytes(bytes); + + var salt = new byte[32]; + rnd.NextBytes(salt); + + var hash = new byte[32]; + rnd.NextBytes(hash); + + Mocker.RangeResponse = bytes; + var len = (ulong)bytes.Length; + + Mocker.RangeHashResponses.Add(ByteString.CopyFrom(hash)); + + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + var param = new PrmRangeHashGet(ContainerId, Mocker.ObjectId, [new FrostFsRange(100, len)], salt); + + var result = await GetClient().GetRangeHashAsync(param); + + Assert.NotNull(Mocker.GetRangeHashRequest); + + Assert.Equal(param.Ranges[0].Offset, Mocker.GetRangeHashRequest.Body.Ranges[0].Offset); + Assert.Equal(param.Ranges[0].Length, Mocker.GetRangeHashRequest.Body.Ranges[0].Length); + + Assert.NotNull(result); + Assert.Single(result); + + Assert.Equal(SHA256.HashData(hash), SHA256.HashData(result.First().ToArray())); + } + + [Fact] + public async void PatchTest() + { + Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); + + var address = new FrostFsAddress(ContainerId, Mocker.ObjectId); + + Mocker.ResultObjectIds!.Add(SHA256.HashData([])); + + Random rnd = new(); + var patch = new byte[32]; + rnd.NextBytes(patch); + + var range = new FrostFsRange(8, (ulong)patch.Length); + + var param = new PrmObjectPatch(address) + { + Payload = new MemoryStream(patch), + MaxPayloadPatchChunkLength = 32, + Range = range + }; + + var result = await GetClient().PatchObjectAsync(param); + + Assert.NotNull(result); + + Assert.NotNull(result.Value); + + Assert.NotNull(Mocker.PatchStreamWriter); + Assert.Single(Mocker.PatchStreamWriter.Messages); + + var sentMessages = Mocker.PatchStreamWriter!.Messages; + + var body = sentMessages.First().GetBody() as Object.PatchRequest.Types.Body; + + Assert.NotNull(body); + + Assert.True(Mocker.PatchStreamWriter.CompletedTask); + + Assert.Equal(address.ContainerId, body.Address.ContainerId); + Assert.Equal(address.ObjectId, body.Address.ObjectId); + + Assert.Equal(32, body.Patch.Chunk.Length); + + Assert.Equal(SHA256.HashData(patch), SHA256.HashData(body.Patch.Chunk.ToArray())); + } } diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs index 426aa9b..56acfc3 100644 --- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs @@ -247,37 +247,108 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) + { + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + await Cleanup(client); + + bool callbackInvoked = false; + var ctx = new CallContext + { + // Timeout = TimeSpan.FromSeconds(20), + Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }) + }; + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); + + var createdContainer = await client.CreateContainerAsync(createContainerParam); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx)); + Assert.NotNull(container); + Assert.True(callbackInvoked); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut(new CallContext + { + Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }) + { + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var objectId = await client.PutObjectAsync(param); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + { + hasObject = true; + + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void PatchTest() { using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); await Cleanup(client); - bool callbackInvoked = false; - var ctx = new CallContext - { - // Timeout = TimeSpan.FromSeconds(20), - Callback = new((CallStatistics cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }) - }; - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); var createdContainer = await client.CreateContainerAsync(createContainerParam); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); Assert.NotNull(container); - Assert.True(callbackInvoked); - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut(new CallContext + var bytes = new byte[1024]; + for (int i = 0; i < 1024; i++) { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }) + bytes[i] = (byte)31; + } + + var param = new PrmObjectPut { Header = new FrostFsObjectHeader( containerId: createdContainer, @@ -289,24 +360,24 @@ public class SmokeClientTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + var patch = new byte[16]; + for (int i = 0; i < 16; i++) { - hasObject = true; - - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); + patch[i] = (byte)32; } - Assert.True(hasObject); + var range = new FrostFsRange(8, (ulong)patch.Length); - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + var patchParams = new PrmObjectPatch(new FrostFsAddress(createdContainer, objectId)) + { + Payload = new MemoryStream(patch), + MaxPayloadPatchChunkLength = 32, + Range = range + }; + + var newIbjId = await client.PatchObjectAsync(patchParams); + + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId)); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -317,8 +388,121 @@ public class SmokeClientTests : SmokeTestsBase ms.Write(chunk.Value.Span); } - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + for(int i = 0; i < (int)range.Offset; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + var rangeEnd = range.Offset + range.Length; + + for (int i = (int)range.Offset; i < (int)rangeEnd; i++) + Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); + + for (int i = (int)rangeEnd; i < bytes.Length; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void RangeTest() + { + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); + + var createdContainer = await client.CreateContainerAsync(createContainerParam); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + Assert.NotNull(container); + + var bytes = new byte[256]; + for (int i = 0; i < 256; i++) + { + bytes[i] = (byte)i; + } + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular), + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var objectId = await client.PutObjectAsync(param); + + var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); + + var rangeReader = await client.GetRangeAsync(rangeParam); + + var downloadedBytes = new byte[rangeParam.Range.Length]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await rangeReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(100, 64)), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void RangeHashTest() + { + using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); + + var createdContainer = await client.CreateContainerAsync(createContainerParam); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + Assert.NotNull(container); + + var bytes = new byte[256]; + for (int i = 0; i < 256; i++) + { + bytes[i] = (byte)i; + } + + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular), + Payload = new MemoryStream(bytes), + ClientCut = false + }; + + var objectId = await client.PutObjectAsync(param); + + var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [ new FrostFsRange(100, 64)], bytes); + + var hashes = await client.GetRangeHashAsync(rangeParam); + + foreach (var hash in hashes) + { + var x = hash.Slice(0, 32).ToArray(); + } + await Cleanup(client); await foreach (var _ in client.ListContainersAsync()) diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index 6b42144..e0d53d7 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -7,6 +7,8 @@ namespace FrostFS.SDK.SmokeTests; public abstract class SmokeTestsBase { + // internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; + internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; internal readonly string url = "http://172.23.32.4:8080"; From 14dc76898e8e0a3fcdc1b0e08c6de3dcd8786264 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 14 Nov 2024 11:24:14 +0300 Subject: [PATCH 28/65] [#25] Client Implement PAtch and Range methods Fix the specific type in interfaces Signed-off-by: Pavel Gross --- src/FrostFS.SDK.ClientV2/FrostFSClient.cs | 2 +- src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs | 2 +- src/FrostFS.SDK.ClientV2/Pool/Pool.cs | 2 +- src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs index 830eb94..a7bfff4 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/FrostFSClient.cs @@ -294,7 +294,7 @@ public class FrostFSClient : IFrostFSClient return service.GetRangeAsync(args); } - public Task>> GetRangeHashAsync(PrmRangeHashGet args) + public Task[]> GetRangeHashAsync(PrmRangeHashGet args) { if (args is null) throw new ArgumentNullException(nameof(args)); diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs index 48ab8e8..8e4bae4 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs @@ -44,7 +44,7 @@ public interface IFrostFSClient : IDisposable Task GetRangeAsync(PrmRangeGet args); - Task>> GetRangeHashAsync(PrmRangeHashGet args); + Task[]> GetRangeHashAsync(PrmRangeHashGet args); Task PutObjectAsync(PrmObjectPut args); diff --git a/src/FrostFS.SDK.ClientV2/Pool/Pool.cs b/src/FrostFS.SDK.ClientV2/Pool/Pool.cs index 9e34551..00abda2 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/Pool.cs +++ b/src/FrostFS.SDK.ClientV2/Pool/Pool.cs @@ -738,7 +738,7 @@ public partial class Pool : IFrostFSClient return await client.Client!.GetRangeAsync(args).ConfigureAwait(false); } - public async Task>> GetRangeHashAsync(PrmRangeHashGet args) + public async Task[]> GetRangeHashAsync(PrmRangeHashGet args) { if (args is null) { diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs index bb742a5..322fb3e 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs @@ -150,7 +150,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return new RangeReader(call); } - internal async Task>> GetRangeHashAsync(PrmRangeHashGet args) + internal async Task[]> GetRangeHashAsync(PrmRangeHashGet args) { var ctx = args.Context!; @@ -197,7 +197,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl Verifier.CheckResponse(response); - var hashCollection = response.Body.HashList.ToArray().Select(h => h.Memory); + var hashCollection = response.Body.HashList.Select(h => h.Memory).ToArray(); return hashCollection; } From c406df1a783347f55aa9b729600f0279878ed974 Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Wed, 13 Nov 2024 12:07:14 +0300 Subject: [PATCH 29/65] [#13] Execute .NET code analyzers in CI Signed-off-by: Vitaliy Potyarkin --- .forgejo/workflows/lint-build.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .forgejo/workflows/lint-build.yml diff --git a/.forgejo/workflows/lint-build.yml b/.forgejo/workflows/lint-build.yml new file mode 100644 index 0000000..ce8b798 --- /dev/null +++ b/.forgejo/workflows/lint-build.yml @@ -0,0 +1,22 @@ +name: lint-build +on: + push: + pull_request: + +jobs: + lint-build: + name: dotnet${{ matrix.dotnet }} + runs-on: docker + container: git.frostfs.info/truecloudlab/env:dotnet-${{ matrix.dotnet }} + strategy: + matrix: + dotnet: + - '8.0' + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Dotnet runs code analyzers on build (if configured): + # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?tabs=net-8#enable-on-build + - run: dotnet build From 766f61a5f7da6db0a5bb3dcfcec4d9978220d663 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 18 Nov 2024 11:11:17 +0300 Subject: [PATCH 30/65] [#26] All: Remove V2 from naming Rename project, namespaces and class names Signed-off-by: Pavel Gross --- FrostFS.SDK.sln | 4 +- .../Caches.cs | 2 +- .../CllientKey.cs | 2 +- .../Exceptions/FrostFsException.cs | 2 +- .../FrostFsInvalidObjectException.cs | 2 +- .../Exceptions/FrostFsResponseException.cs | 2 +- .../Exceptions/FrostFsStreamException.cs | 2 +- .../Exceptions/SessionExpiredException.cs | 2 +- .../Exceptions/SessionNotFoundException.cs | 2 +- .../FrostFS.SDK.Client.csproj} | 6 +- .../FrostFSClient.cs | 6 +- .../GlobalSuppressions.cs | 2 +- .../Interceptors/ErrorInterceptor.cs | 2 +- .../Interceptors/MetricsInterceptor.cs | 2 +- .../Interfaces/IFrostFSClient.cs | 3 +- .../Logging/FrostFsMessages.cs | 2 +- .../Mappers/Container.cs | 2 +- .../Mappers/ContainerId.cs | 2 +- .../Mappers/MetaHeader.cs | 2 +- .../Mappers/Netmap/Netmap.cs | 2 +- .../Mappers/Netmap/NodeInfo.cs | 4 +- .../Mappers/Netmap/PlacementPolicy.cs | 2 +- .../Mappers/Netmap/Replica.cs | 2 +- .../Mappers/Object/Object.cs | 2 +- .../Mappers/Object/ObjectAttributeMapper.cs | 2 +- .../Mappers/Object/ObjectFilterMapper.cs | 2 +- .../Mappers/Object/ObjectHeaderMapper.cs | 2 +- .../Mappers/Object/ObjectId.cs | 2 +- .../Mappers/OwnerId.cs | 2 +- .../Mappers/Session/SessionMapper.cs | 2 +- .../Mappers/SignatureMapper.cs | 2 +- .../Mappers/Status.cs | 2 +- .../Mappers/Version.cs | 2 +- .../Models/Chain/ChainTarget.cs | 2 +- .../Models/Chain/FrostFsChain.cs | 2 +- .../Models/Chain/FrostFsTargetType.cs | 2 +- .../Models/Client/ClientSettings.cs | 0 .../Models/Containers/FrostFsContainerId.cs | 4 +- .../Models/Containers/FrostFsContainerInfo.cs | 4 +- .../Models/Enums/FrostFsMatchType.cs | 0 .../Models/Enums/FrostFsObjectType.cs | 0 .../Models/Enums/FrostFsStatusCode.cs | 0 .../Models/Enums/NodeState.cs | 0 .../Models/Enums/SignatureScheme.cs | 0 .../Models/Misc/CallStatistics.cs | 0 .../Models/Misc/CheckSum.cs | 0 .../Models/Misc/Constants.cs | 0 .../Models/Netmap/FrostFsNetmapSnapshot.cs | 0 .../Models/Netmap/FrostFsNodeInfo.cs | 0 .../Models/Netmap/FrostFsPlacementPolicy.cs | 2 +- .../Models/Netmap/FrostFsReplica.cs | 0 .../Models/Netmap/FrostFsVersion.cs | 2 +- .../Models/Object/FrostFsAddress.cs | 2 +- .../Models/Object/FrostFsAttributePair.cs | 0 .../Models/Object/FrostFsLargeObject.cs | 0 .../Models/Object/FrostFsLinkObject.cs | 0 .../Models/Object/FrostFsObject.cs | 0 .../Models/Object/FrostFsObjectFilter.cs | 0 .../Models/Object/FrostFsObjectHeader.cs | 2 +- .../Models/Object/FrostFsObjectId.cs | 0 .../Models/Object/FrostFsOwner.cs | 2 +- .../Models/Object/FrostFsRange.cs | 0 .../Models/Object/FrostFsSplit.cs | 0 .../Models/Object/IObjectReader.cs | 0 .../Models/Object/SplitId.cs | 0 .../Models/Response/FrostFsResponseStatus.cs | 0 .../Models/Response/FrostFsSignature.cs | 0 .../Models/Response/MetaHeader.cs | 0 .../Models/Session/FrostFsSessionToken.cs | 0 .../Parameters/CallContext.cs | 2 +- .../Parameters/Credentials.cs | 2 +- .../Parameters/IContext.cs | 2 +- .../Parameters/ISessionToken.cs | 2 +- .../Parameters/PrmApeChainList.cs | 2 +- .../Parameters/PrmApeChainRemove.cs | 2 +- .../Parameters/PrmApeRemoveAdd.cs | 2 +- .../Parameters/PrmBalance.cs | 2 +- .../Parameters/PrmBase.cs | 2 +- .../Parameters/PrmContainerCreate.cs | 2 +- .../Parameters/PrmContainerDelete.cs | 2 +- .../Parameters/PrmContainerGet.cs | 2 +- .../Parameters/PrmContainerGetAll.cs | 2 +- .../Parameters/PrmNetmapSnapshot.cs | 2 +- .../Parameters/PrmNetworkSettings.cs | 2 +- .../Parameters/PrmNodeInfo.cs | 2 +- .../Parameters/PrmObjectDelete.cs | 2 +- .../Parameters/PrmObjectGet.cs | 2 +- .../Parameters/PrmObjectHeadGet.cs | 2 +- .../Parameters/PrmObjectPatch.cs | 2 +- .../Parameters/PrmObjectPut.cs | 2 +- .../Parameters/PrmObjectSearch.cs | 2 +- .../Parameters/PrmRangeGet.cs | 2 +- .../Parameters/PrmRangeHashGet.cs | 2 +- .../Parameters/PrmSessionCreate.cs | 2 +- .../Parameters/PrmSingleObjectPut.cs | 2 +- .../Parameters/PrmWait.cs | 2 +- .../Pool/ClientStatusMonitor.cs | 2 +- .../Pool/ClientWrapper.cs | 40 +++++-- .../Pool/HealthyStatus.cs | 2 +- .../Pool/IClientStatus.cs | 2 +- .../Pool/InitParameters.cs | 2 +- .../Pool/InnerPool.cs | 2 +- .../Pool/MethodIndex.cs | 2 +- .../Pool/MethodStatus.cs | 2 +- .../Pool/NodeParam.cs | 2 +- .../Pool/NodeStatistic.cs | 2 +- .../Pool/NodesParam.cs | 2 +- .../Pool/Pool.cs | 6 +- .../Pool/RebalanceParameters.cs | 2 +- .../Pool/RequestInfo.cs | 2 +- .../Pool/Sampler.cs | 2 +- .../Pool/SessionCache.cs | 2 +- .../Pool/Statistic.cs | 2 +- .../Pool/StatusSnapshot.cs | 2 +- .../Pool/WorkList.cs | 2 +- .../Pool/WrapperPrm.cs | 2 +- .../Services/AccountingServiceProvider.cs | 2 +- .../Services/ApeManagerServiceProvider.cs | 2 +- .../Services/ContainerServiceProvider.cs | 6 +- .../Services/NetmapServiceProvider.cs | 2 +- .../Services/ObjectServiceProvider.cs | 6 +- .../Services/SessionServiceProvider.cs | 6 +- .../Services/Shared/ContextAccessor.cs | 2 +- .../Services/Shared/SessionProvider.cs | 2 +- .../Tools/ClientContext.cs | 2 +- .../Tools/NetworkSettings.cs | 2 +- .../Tools/ObjectReader.cs | 2 +- .../Tools/ObjectStreamer.cs | 2 +- .../Tools/ObjectTools.cs | 4 +- .../Tools/Range.cs | 0 .../Tools/RangeReader.cs | 2 +- .../Tools/RequestConstructor.cs | 6 +- .../Tools/RequestSigner.cs | 4 +- .../Tools/SearchReader.cs | 2 +- .../Tools/Verifier.cs | 6 +- src/FrostFS.SDK.ClientV2/Ростелеком.txt | 8 -- .../FrostFS.SDK.Cryptography.csproj | 10 +- .../Client/ClientSettings.cs | 63 ---------- .../Containers/ContainerId.cs | 11 -- .../Containers/FrostFsContainer.cs | 11 -- src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs | 21 ---- .../Enums/FrostFsObjectMatchType.cs | 10 -- .../Enums/FrostFsObjectType.cs | 8 -- .../Enums/FrostFsStatusCode.cs | 22 ---- src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs | 9 -- .../Enums/SignatureScheme.cs | 8 -- .../FrostFS.SDK.ModelsV2.csproj | 22 ---- .../Misc/CallStatistics.cs | 7 -- src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs | 20 ---- src/FrostFS.SDK.ModelsV2/Misc/Constants.cs | 52 -------- .../Netmap/FrostFsNodeInfo.cs | 18 --- .../Netmap/FrostFsPlacementPolicy.cs | 28 ----- .../Netmap/FrostFsReplica.cs | 15 --- .../Netmap/FrostFsVersion.cs | 17 --- src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs | 10 -- .../Object/FrostFsObject.cs | 83 ------------- .../Object/IObjectReader.cs | 10 -- .../Object/ObjectAttribute.cs | 7 -- .../Object/ObjectFilter.cs | 111 ------------------ .../Object/ObjectHeader.cs | 26 ---- src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs | 28 ----- src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs | 25 ---- src/FrostFS.SDK.ModelsV2/Object/SplitId.cs | 62 ---------- src/FrostFS.SDK.ModelsV2/Object/Splitter.cs | 24 ---- .../Response/FrostFsResponseStatus.cs | 14 --- .../Response/FrostFsSignature.cs | 10 -- .../Response/MetaHeader.cs | 20 ---- .../Session/FrostFsSessionToken.cs | 6 - .../FrostFS.SDK.Protos.csproj} | 10 +- .../Interfaces/IMetaHeader.cs | 0 .../Interfaces/IRequest.cs | 2 +- .../Interfaces/IResponse.cs | 0 .../Interfaces/IVerifiableMessage.cs | 0 .../Interfaces/IVerificationHeader.cs | 0 .../accounting/Extension.Message.cs | 2 +- .../accounting/service.proto | 0 .../accounting/types.proto | 0 .../acl/types.proto | 0 .../ape/types.proto | 0 .../apemanager/Extension.Message.cs | 2 +- .../apemanager/service.proto | 0 .../container/Extension.Message.cs | 2 +- .../container/service.proto | 0 .../container/types.proto | 0 .../lock/types.proto | 0 .../netmap/Extension.Message.cs | 2 +- .../netmap/service.proto | 0 .../netmap/types.proto | 0 .../object/Extension.Message.cs | 2 +- .../object/service.proto | 0 .../object/types.proto | 0 .../refs/types.proto | 0 .../session/Extension.Message.cs | 2 +- .../session/Extension.MetaHeader.cs | 0 .../session/Extension.VerificationHeader.cs | 0 .../session/Extension.XHeader.cs | 0 .../session/service.proto | 0 .../session/types.proto | 0 .../status/types.proto | 0 .../tombstone/types.proto | 0 src/FrostFS.SDK.Tests/ContainerTest.cs | 4 +- src/FrostFS.SDK.Tests/ContainerTestsBase.cs | 4 +- .../FrostFS.SDK.Tests.csproj | 12 +- .../Mocks/AsyncStreamRangeReaderMock.cs | 4 +- .../Mocks/AsyncStreamReaderMock.cs | 4 +- .../Mocks/ClientStreamWriter.cs | 2 +- .../ContainerServiceBase.cs | 4 +- .../ContainerServiceMocks/GetContainerMock.cs | 4 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 4 +- .../Mocks/PatchStreamWriter.cs | 2 +- src/FrostFS.SDK.Tests/NetworkTest.cs | 2 +- src/FrostFS.SDK.Tests/NetworkTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 4 +- src/FrostFS.SDK.Tests/ObjectTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 2 +- src/FrostFS.SDK.Tests/SessionTests.cs | 4 +- src/FrostFS.SDK.Tests/SessionTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/SmokeClientTests.cs | 4 +- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 2 +- 219 files changed, 219 insertions(+), 974 deletions(-) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Caches.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/CllientKey.cs (89%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/FrostFsException.cs (90%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/FrostFsInvalidObjectException.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/FrostFsResponseException.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/FrostFsStreamException.cs (90%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/SessionExpiredException.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Exceptions/SessionNotFoundException.cs (91%) rename src/{FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj => FrostFS.SDK.Client/FrostFS.SDK.Client.csproj} (85%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/FrostFSClient.cs (99%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/GlobalSuppressions.cs (87%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Interceptors/ErrorInterceptor.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Interceptors/MetricsInterceptor.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Interfaces/IFrostFSClient.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Logging/FrostFsMessages.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Container.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/ContainerId.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/MetaHeader.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Netmap/Netmap.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Netmap/NodeInfo.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Netmap/PlacementPolicy.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Netmap/Replica.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Object/Object.cs (85%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Object/ObjectAttributeMapper.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Object/ObjectFilterMapper.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Object/ObjectHeaderMapper.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Object/ObjectId.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/OwnerId.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Session/SessionMapper.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/SignatureMapper.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Status.cs (92%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Mappers/Version.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Chain/ChainTarget.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Chain/FrostFsChain.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Chain/FrostFsTargetType.cs (74%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Client/ClientSettings.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Containers/FrostFsContainerId.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Containers/FrostFsContainerInfo.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Enums/FrostFsMatchType.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Enums/FrostFsObjectType.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Enums/FrostFsStatusCode.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Enums/NodeState.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Enums/SignatureScheme.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Misc/CallStatistics.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Misc/CheckSum.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Misc/Constants.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Netmap/FrostFsNetmapSnapshot.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Netmap/FrostFsNodeInfo.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Netmap/FrostFsPlacementPolicy.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Netmap/FrostFsReplica.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Netmap/FrostFsVersion.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsAddress.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsAttributePair.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsLargeObject.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsLinkObject.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsObject.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsObjectFilter.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsObjectHeader.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsObjectId.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsOwner.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsRange.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/FrostFsSplit.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/IObjectReader.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Object/SplitId.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Response/FrostFsResponseStatus.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Response/FrostFsSignature.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Response/MetaHeader.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Models/Session/FrostFsSessionToken.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/CallContext.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/Credentials.cs (85%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/IContext.cs (89%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/ISessionToken.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmApeChainList.cs (82%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmApeChainRemove.cs (87%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmApeRemoveAdd.cs (86%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmBalance.cs (68%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmBase.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmContainerCreate.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmContainerDelete.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmContainerGet.cs (83%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmContainerGetAll.cs (70%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmNetmapSnapshot.cs (70%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmNetworkSettings.cs (70%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmNodeInfo.cs (68%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectDelete.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectGet.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectHeadGet.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectPatch.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectPut.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmObjectSearch.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmRangeGet.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmRangeHashGet.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmSessionCreate.cs (81%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmSingleObjectPut.cs (88%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Parameters/PrmWait.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/ClientStatusMonitor.cs (99%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/ClientWrapper.cs (81%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/HealthyStatus.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/IClientStatus.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/InitParameters.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/InnerPool.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/MethodIndex.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/MethodStatus.cs (91%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/NodeParam.cs (92%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/NodeStatistic.cs (87%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/NodesParam.cs (88%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/Pool.cs (99%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/RebalanceParameters.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/RequestInfo.cs (87%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/Sampler.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/SessionCache.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/Statistic.cs (88%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/StatusSnapshot.cs (79%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/WorkList.cs (93%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Pool/WrapperPrm.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/AccountingServiceProvider.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/ApeManagerServiceProvider.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/ContainerServiceProvider.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/NetmapServiceProvider.cs (99%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/ObjectServiceProvider.cs (99%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/SessionServiceProvider.cs (94%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/Shared/ContextAccessor.cs (79%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Services/Shared/SessionProvider.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/ClientContext.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/NetworkSettings.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/ObjectReader.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/ObjectStreamer.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/ObjectTools.cs (97%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/Range.cs (100%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/RangeReader.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/RequestConstructor.cs (95%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/RequestSigner.cs (98%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/SearchReader.cs (96%) rename src/{FrostFS.SDK.ClientV2 => FrostFS.SDK.Client}/Tools/Verifier.cs (97%) delete mode 100644 src/FrostFS.SDK.ClientV2/Ростелеком.txt delete mode 100644 src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj delete mode 100644 src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Misc/Constants.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/SplitId.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Object/Splitter.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs delete mode 100644 src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs rename src/{FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj => FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj} (88%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/Interfaces/IMetaHeader.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/Interfaces/IRequest.cs (80%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/Interfaces/IResponse.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/Interfaces/IVerifiableMessage.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/Interfaces/IVerificationHeader.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/accounting/Extension.Message.cs (97%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/accounting/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/accounting/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/acl/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/ape/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/apemanager/Extension.Message.cs (98%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/apemanager/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/container/Extension.Message.cs (99%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/container/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/container/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/lock/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/netmap/Extension.Message.cs (98%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/netmap/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/netmap/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/object/Extension.Message.cs (99%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/object/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/object/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/refs/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/Extension.Message.cs (96%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/Extension.MetaHeader.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/Extension.VerificationHeader.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/Extension.XHeader.cs (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/service.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/session/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/status/types.proto (100%) rename src/{FrostFS.SDK.ProtosV2 => FrostFS.SDK.Protos}/tombstone/types.proto (100%) diff --git a/FrostFS.SDK.sln b/FrostFS.SDK.sln index 9d9fbbe..b96bda2 100644 --- a/FrostFS.SDK.sln +++ b/FrostFS.SDK.sln @@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ClientV2", "src\FrostFS.SDK.ClientV2\FrostFS.SDK.ClientV2.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Client", "src\FrostFS.SDK.Client\FrostFS.SDK.Client.csproj", "{50D8F61F-C302-4AC9-8D8A-AB0B8C0988C3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Cryptography", "src\FrostFS.SDK.Cryptography\FrostFS.SDK.Cryptography.csproj", "{3D804F4A-B0B2-47A5-B006-BE447BE64B50}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.ProtosV2", "src\FrostFS.SDK.ProtosV2\FrostFS.SDK.ProtosV2.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Protos", "src\FrostFS.SDK.Protos\FrostFS.SDK.Protos.csproj", "{5012EF96-9C9E-4E77-BC78-B4111EE54107}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FrostFS.SDK.Tests", "src\FrostFS.SDK.Tests\FrostFS.SDK.Tests.csproj", "{8FDA7E0D-9C75-4874-988E-6592CD28F76C}" EndProject diff --git a/src/FrostFS.SDK.ClientV2/Caches.cs b/src/FrostFS.SDK.Client/Caches.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Caches.cs rename to src/FrostFS.SDK.Client/Caches.cs index ae6a290..9f2b3c2 100644 --- a/src/FrostFS.SDK.ClientV2/Caches.cs +++ b/src/FrostFS.SDK.Client/Caches.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Caching.Memory; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal static class Caches { diff --git a/src/FrostFS.SDK.ClientV2/CllientKey.cs b/src/FrostFS.SDK.Client/CllientKey.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/CllientKey.cs rename to src/FrostFS.SDK.Client/CllientKey.cs index a7542cb..cdafb3f 100644 --- a/src/FrostFS.SDK.ClientV2/CllientKey.cs +++ b/src/FrostFS.SDK.Client/CllientKey.cs @@ -4,7 +4,7 @@ using FrostFS.SDK.Cryptography; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class ClientKey(ECDsa key) { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs similarity index 90% rename from src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs rename to src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs index ba31b9d..9f3f36c 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class FrostFsException : Exception { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsInvalidObjectException.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs rename to src/FrostFS.SDK.Client/Exceptions/FrostFsInvalidObjectException.cs index 87e20e7..de1a88d 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsInvalidObjectException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsInvalidObjectException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class FrostFsInvalidObjectException : FrostFsException { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs rename to src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs index 6ef30aa..61f4e98 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsResponseException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class FrostFsResponseException : FrostFsException { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs similarity index 90% rename from src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs rename to src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs index 45472e4..0bd40d5 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/FrostFsStreamException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsStreamException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class FrostFsStreamException : FrostFsException { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs b/src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs rename to src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs index 44404dd..0fa6a82 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/SessionExpiredException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/SessionExpiredException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class SessionExpiredException : FrostFsException { diff --git a/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs b/src/FrostFS.SDK.Client/Exceptions/SessionNotFoundException.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs rename to src/FrostFS.SDK.Client/Exceptions/SessionNotFoundException.cs index 5ec0a09..c5be848 100644 --- a/src/FrostFS.SDK.ClientV2/Exceptions/SessionNotFoundException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/SessionNotFoundException.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class SessionNotFoundException : FrostFsException { diff --git a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj similarity index 85% rename from src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj rename to src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index fefaaf2..e175d13 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFS.SDK.ClientV2.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -8,11 +8,11 @@ - true + true - true + true @@ -30,7 +30,7 @@ - + diff --git a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs similarity index 99% rename from src/FrostFS.SDK.ClientV2/FrostFSClient.cs rename to src/FrostFS.SDK.Client/FrostFSClient.cs index a7bfff4..4112a5b 100644 --- a/src/FrostFS.SDK.ClientV2/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -10,8 +10,8 @@ using FrostFS.Accounting; using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; -using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2.Services; +using FrostFS.SDK.Client.Interfaces; +using FrostFS.SDK.Client.Services; using FrostFS.SDK.Cryptography; using FrostFS.Session; @@ -21,7 +21,7 @@ using Grpc.Net.Client; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class FrostFSClient : IFrostFSClient { diff --git a/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs b/src/FrostFS.SDK.Client/GlobalSuppressions.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs rename to src/FrostFS.SDK.Client/GlobalSuppressions.cs index c87cda6..29a5b5c 100644 --- a/src/FrostFS.SDK.ClientV2/GlobalSuppressions.cs +++ b/src/FrostFS.SDK.Client/GlobalSuppressions.cs @@ -5,4 +5,4 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "", Scope = "member", Target = "~M:FrostFS.SDK.ClientV2.Sampler.Next~System.Int32")] +[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "", Scope = "member", Target = "~M:FrostFS.SDK.Client.Sampler.Next~System.Int32")] diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs b/src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs rename to src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs index d76477f..771a0f2 100644 --- a/src/FrostFS.SDK.ClientV2/Interceptors/ErrorInterceptor.cs +++ b/src/FrostFS.SDK.Client/Interceptors/ErrorInterceptor.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Grpc.Core; using Grpc.Core.Interceptors; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "parameters are provided by GRPC infrastructure")] diff --git a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs b/src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs rename to src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs index f4d93c0..414551e 100644 --- a/src/FrostFS.SDK.ClientV2/Interceptors/MetricsInterceptor.cs +++ b/src/FrostFS.SDK.Client/Interceptors/MetricsInterceptor.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Grpc.Core; using Grpc.Core.Interceptors; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "parameters are provided by GRPC infrastructure")] diff --git a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs rename to src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 8e4bae4..456962f 100644 --- a/src/FrostFS.SDK.ClientV2/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -3,7 +3,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Frostfs.V2.Ape; -namespace FrostFS.SDK.ClientV2.Interfaces; + +namespace FrostFS.SDK.Client.Interfaces; public interface IFrostFSClient : IDisposable { diff --git a/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs b/src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs rename to src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs index 2fd1fe1..b5e78ab 100644 --- a/src/FrostFS.SDK.ClientV2/Logging/FrostFsMessages.cs +++ b/src/FrostFS.SDK.Client/Logging/FrostFsMessages.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Logging; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal static partial class FrostFsMessages { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs b/src/FrostFS.SDK.Client/Mappers/Container.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Mappers/Container.cs rename to src/FrostFS.SDK.Client/Mappers/Container.cs index e631472..7355c4a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Container.cs +++ b/src/FrostFS.SDK.Client/Mappers/Container.cs @@ -3,7 +3,7 @@ using System.Linq; using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ContainerMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs rename to src/FrostFS.SDK.Client/Mappers/ContainerId.cs index c4c805c..fe8632f 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs @@ -7,7 +7,7 @@ using Google.Protobuf; using Microsoft.Extensions.Caching.Memory; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ContainerIdMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs b/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs rename to src/FrostFS.SDK.Client/Mappers/MetaHeader.cs index 5bf994e..e4ab8a2 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/MetaHeader.cs +++ b/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs @@ -2,7 +2,7 @@ using System; using FrostFS.Session; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class MetaHeaderMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs rename to src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs index 6729501..be378fc 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Netmap.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Netmap.cs @@ -3,7 +3,7 @@ using System.Linq; using FrostFS.Netmap; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class NetmapMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs rename to src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs index d882127..747468b 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs @@ -2,9 +2,9 @@ using System; using System.Linq; using FrostFS.Netmap; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class NodeInfoMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs rename to src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs index c44a397..5f1600c 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs @@ -3,7 +3,7 @@ using System.Linq; using FrostFS.Netmap; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class PlacementPolicyMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs rename to src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs index d9ea0cd..f0ace94 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs @@ -2,7 +2,7 @@ using System; using FrostFS.Netmap; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class ReplicaMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs b/src/FrostFS.SDK.Client/Mappers/Object/Object.cs similarity index 85% rename from src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs rename to src/FrostFS.SDK.Client/Mappers/Object/Object.cs index d9f5d13..b9d1520 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/Object.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; internal static class ObjectMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs rename to src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs index 85c7c5a..cfec8fd 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectAttributeMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs @@ -2,7 +2,7 @@ using System; using FrostFS.Object; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ObjectAttributeMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs rename to src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs index 76d1c92..249045d 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectFilterMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectFilterMapper.cs @@ -2,7 +2,7 @@ using System; using FrostFS.Object; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ObjectFilterMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs rename to src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs index bdeb862..1ee29a6 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs @@ -5,7 +5,7 @@ using System.Linq; using FrostFS.Object; using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ObjectHeaderMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs rename to src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs index 10f8409..373edfb 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs @@ -4,7 +4,7 @@ using FrostFS.Refs; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ObjectIdMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs rename to src/FrostFS.SDK.Client/Mappers/OwnerId.cs index c2e61e6..297178a 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs @@ -7,7 +7,7 @@ using Google.Protobuf; using Microsoft.Extensions.Caching.Memory; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class OwnerIdMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs b/src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs rename to src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs index 9f4a36f..d1307e8 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Session/SessionMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs @@ -2,7 +2,7 @@ using System; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class SessionMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs rename to src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs index d3bf988..88b4ac8 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/SignatureMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs @@ -2,7 +2,7 @@ using System; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class SignatureMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs b/src/FrostFS.SDK.Client/Mappers/Status.cs similarity index 92% rename from src/FrostFS.SDK.ClientV2/Mappers/Status.cs rename to src/FrostFS.SDK.Client/Mappers/Status.cs index ad9ea7d..f39b1fe 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Status.cs +++ b/src/FrostFS.SDK.Client/Mappers/Status.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class StatusMapper { diff --git a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs b/src/FrostFS.SDK.Client/Mappers/Version.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Mappers/Version.cs rename to src/FrostFS.SDK.Client/Mappers/Version.cs index d4cc254..a66df6e 100644 --- a/src/FrostFS.SDK.ClientV2/Mappers/Version.cs +++ b/src/FrostFS.SDK.Client/Mappers/Version.cs @@ -3,7 +3,7 @@ using System.Threading; using FrostFS.Refs; -namespace FrostFS.SDK.ClientV2.Mappers.GRPC; +namespace FrostFS.SDK.Client.Mappers.GRPC; public static class VersionMapper { diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs rename to src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs index a009df2..dbdde51 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/ChainTarget.cs +++ b/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs @@ -2,7 +2,7 @@ using Frostfs.V2.Ape; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatable { diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs rename to src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs index 1f4d251..70bf093 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsChain.cs +++ b/src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs @@ -1,6 +1,6 @@ using Google.Protobuf; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public struct FrostFsChain(byte[] raw) : System.IEquatable { diff --git a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs similarity index 74% rename from src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs rename to src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs index ed5fa27..d5379f8 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Chain/FrostFsTargetType.cs +++ b/src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public enum FrostFsTargetType { diff --git a/src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Client/ClientSettings.cs rename to src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs rename to src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs index 06b87ca..f0b3fdf 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerId.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs @@ -1,6 +1,6 @@ using FrostFS.Refs; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs rename to src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs index affdbee..6223a7f 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs @@ -2,8 +2,8 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsMatchType.cs b/src/FrostFS.SDK.Client/Models/Enums/FrostFsMatchType.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsMatchType.cs rename to src/FrostFS.SDK.Client/Models/Enums/FrostFsMatchType.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs b/src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsObjectType.cs rename to src/FrostFS.SDK.Client/Models/Enums/FrostFsObjectType.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs b/src/FrostFS.SDK.Client/Models/Enums/FrostFsStatusCode.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Enums/FrostFsStatusCode.cs rename to src/FrostFS.SDK.Client/Models/Enums/FrostFsStatusCode.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs b/src/FrostFS.SDK.Client/Models/Enums/NodeState.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Enums/NodeState.cs rename to src/FrostFS.SDK.Client/Models/Enums/NodeState.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs b/src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Enums/SignatureScheme.cs rename to src/FrostFS.SDK.Client/Models/Enums/SignatureScheme.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs b/src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Misc/CallStatistics.cs rename to src/FrostFS.SDK.Client/Models/Misc/CallStatistics.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs b/src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Misc/CheckSum.cs rename to src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs b/src/FrostFS.SDK.Client/Models/Misc/Constants.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Misc/Constants.cs rename to src/FrostFS.SDK.Client/Models/Misc/Constants.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNetmapSnapshot.cs rename to src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsNodeInfo.cs rename to src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs rename to src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index 62f7fb2..29c9310 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using FrostFS.Netmap; -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Client; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsReplica.cs rename to src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs rename to src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs index e63d9ac..6fd0afc 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Netmap/FrostFsVersion.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs @@ -1,5 +1,5 @@ using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs index 46581d1..bf17f18 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAddress.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsAddress.cs @@ -1,5 +1,5 @@ using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttributePair.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsAttributePair.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsLargeObject.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLargeObject.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsLargeObject.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsLinkObject.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsLinkObject.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObject.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectFilter.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsObjectFilter.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs index ad8887f..4f1c5d0 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectHeader.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectHeader.cs @@ -4,7 +4,7 @@ using System.Linq; using FrostFS.Object; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsObjectId.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs index 0a24164..1bd6754 100644 --- a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsOwner.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsRange.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsRange.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/FrostFsSplit.cs rename to src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs b/src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/IObjectReader.cs rename to src/FrostFS.SDK.Client/Models/Object/IObjectReader.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs b/src/FrostFS.SDK.Client/Models/Object/SplitId.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Object/SplitId.cs rename to src/FrostFS.SDK.Client/Models/Object/SplitId.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Response/FrostFsResponseStatus.cs rename to src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs b/src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Response/FrostFsSignature.cs rename to src/FrostFS.SDK.Client/Models/Response/FrostFsSignature.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs b/src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Response/MetaHeader.cs rename to src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs diff --git a/src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Models/Session/FrostFsSessionToken.cs rename to src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs diff --git a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs b/src/FrostFS.SDK.Client/Parameters/CallContext.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs rename to src/FrostFS.SDK.Client/Parameters/CallContext.cs index 24f076e..323a990 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/CallContext.cs +++ b/src/FrostFS.SDK.Client/Parameters/CallContext.cs @@ -9,7 +9,7 @@ using Google.Protobuf; using Grpc.Core.Interceptors; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class CallContext() { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs b/src/FrostFS.SDK.Client/Parameters/Credentials.cs similarity index 85% rename from src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs rename to src/FrostFS.SDK.Client/Parameters/Credentials.cs index ebc7558..6b712ff 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/Credentials.cs +++ b/src/FrostFS.SDK.Client/Parameters/Credentials.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class Credentials(ECDsa key, FrostFsOwner ownerId) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs b/src/FrostFS.SDK.Client/Parameters/IContext.cs similarity index 89% rename from src/FrostFS.SDK.ClientV2/Parameters/IContext.cs rename to src/FrostFS.SDK.Client/Parameters/IContext.cs index 128c6a0..ebf99a9 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/IContext.cs +++ b/src/FrostFS.SDK.Client/Parameters/IContext.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public interface IContext { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs b/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs rename to src/FrostFS.SDK.Client/Parameters/ISessionToken.cs index cc3ed0f..6d07be0 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/ISessionToken.cs +++ b/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public interface ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs similarity index 82% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs rename to src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs index 4202685..a9a75bb 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainList.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmApeChainList(FrostFsChainTarget target, CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs rename to src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs index a7a9f5a..9773e6d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs similarity index 86% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs rename to src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs index 75ac0e7..b74901f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmApeRemoveAdd.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs b/src/FrostFS.SDK.Client/Parameters/PrmBalance.cs similarity index 68% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs rename to src/FrostFS.SDK.Client/Parameters/PrmBalance.cs index df36980..6544287 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBalance.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmBalance.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs b/src/FrostFS.SDK.Client/Parameters/PrmBase.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs rename to src/FrostFS.SDK.Client/Parameters/PrmBase.cs index e6fb030..092be46 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmBase.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmBase.cs @@ -1,6 +1,6 @@ using System.Collections.Specialized; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs rename to src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs index 68eadc9..ea29967 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs rename to src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs index 9de85f8..e6b1d6d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerDelete.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs similarity index 83% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs rename to src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs index 85f0821..723a4c2 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmContainerGet(FrostFsContainerId container, CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs similarity index 70% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs rename to src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs index ceec0c1..66e2f9b 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs similarity index 70% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs rename to src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs index 0938f86..06a379c 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetmapSnapshot.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs similarity index 70% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs rename to src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs index de2c13d..9731d4d 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNetworkSettings.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs similarity index 68% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs rename to src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs index dbbcdd8..e93b94f 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmNodeInfo.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs index e168b31..9a42958 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs index 11e64d7..a50a657 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs index 0b948b7..84fed94 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs index de292ea..2feb84c 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPatch.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs @@ -1,6 +1,6 @@ using System.IO; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectPatch(FrostFsAddress address, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs index 47a1e1e..582d3f5 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs @@ -1,6 +1,6 @@ using System.IO; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs rename to src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs index 723c1e7..2e6d3d2 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs rename to src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs index 5bcad9f..60286f4 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmRangeGet( FrostFsContainerId containerId, diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs rename to src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs index 661ed64..95853d7 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmRangeHashGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmRangeHashGet( FrostFsContainerId containerId, diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs similarity index 81% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs rename to src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs index ab02f69..7fdc3de 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSessionCreate.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmSessionCreate(ulong expiration, CallContext? ctx = null) : PrmBase(ctx) { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs similarity index 88% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs rename to src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs index 8cb8c19..6e5d180 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmSingleObjectPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken { diff --git a/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs b/src/FrostFS.SDK.Client/Parameters/PrmWait.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs rename to src/FrostFS.SDK.Client/Parameters/PrmWait.cs index d914719..047f446 100644 --- a/src/FrostFS.SDK.ClientV2/Parameters/PrmWait.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmWait.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class PrmWait(TimeSpan timeout, TimeSpan pollInterval) { diff --git a/src/FrostFS.SDK.ClientV2/Pool/ClientStatusMonitor.cs b/src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs similarity index 99% rename from src/FrostFS.SDK.ClientV2/Pool/ClientStatusMonitor.cs rename to src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs index 7200b37..c05c24c 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/ClientStatusMonitor.cs +++ b/src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs @@ -3,7 +3,7 @@ using System.Threading; using Microsoft.Extensions.Logging; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // clientStatusMonitor count error rate and other statistics for connection. public class ClientStatusMonitor : IClientStatus diff --git a/src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs similarity index 81% rename from src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs rename to src/FrostFS.SDK.Client/Pool/ClientWrapper.cs index 206f69e..7e50337 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/ClientWrapper.cs +++ b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Grpc.Core; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // clientWrapper is used by default, alternative implementations are intended for testing purposes only. public class ClientWrapper : ClientStatusMonitor @@ -101,27 +101,41 @@ public class ClientWrapper : ClientStatusMonitor await ScheduleGracefulClose().ConfigureAwait(false); } - //#pragma warning disable CA2000 // Dispose objects before losing scope: will be disposed manually - FrostFSClient client = new(WrapperPrm, sessionCache); - //#pragma warning restore CA2000 + FrostFSClient? client = null; - //TODO: set additioanl params - var error = await client.Dial(ctx).ConfigureAwait(false); - if (!string.IsNullOrEmpty(error)) + try { - SetUnhealthyOnDial(); - return wasHealthy; + client = new(WrapperPrm, sessionCache); + + var dialCtx = new CallContext + { + Timeout = TimeSpan.FromTicks((long)WrapperPrm.DialTimeout), + CancellationToken = ctx.CancellationToken + }; + + var error = await client.Dial(ctx).ConfigureAwait(false); + if (!string.IsNullOrEmpty(error)) + { + SetUnhealthyOnDial(); + return wasHealthy; + } + + lock (_lock) + { + Client = client; + } + + client = null; } - - lock (_lock) + finally { - Client = client; + client?.Dispose(); } try { var prmNodeInfo = new PrmNodeInfo(ctx); - var res = await client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); + var res = await Client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); } catch (FrostFsException) { diff --git a/src/FrostFS.SDK.ClientV2/Pool/HealthyStatus.cs b/src/FrostFS.SDK.Client/Pool/HealthyStatus.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Pool/HealthyStatus.cs rename to src/FrostFS.SDK.Client/Pool/HealthyStatus.cs index f87c1dd..d02e6bb 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/HealthyStatus.cs +++ b/src/FrostFS.SDK.Client/Pool/HealthyStatus.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // values for healthy status of clientStatusMonitor. public enum HealthyStatus diff --git a/src/FrostFS.SDK.ClientV2/Pool/IClientStatus.cs b/src/FrostFS.SDK.Client/Pool/IClientStatus.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Pool/IClientStatus.cs rename to src/FrostFS.SDK.Client/Pool/IClientStatus.cs index cbf597e..0c08fac 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/IClientStatus.cs +++ b/src/FrostFS.SDK.Client/Pool/IClientStatus.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public interface IClientStatus { diff --git a/src/FrostFS.SDK.ClientV2/Pool/InitParameters.cs b/src/FrostFS.SDK.Client/Pool/InitParameters.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Pool/InitParameters.cs rename to src/FrostFS.SDK.Client/Pool/InitParameters.cs index f02bd9e..480820e 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/InitParameters.cs +++ b/src/FrostFS.SDK.Client/Pool/InitParameters.cs @@ -5,7 +5,7 @@ using Grpc.Net.Client; using Microsoft.Extensions.Logging; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // InitParameters contains values used to initialize connection Pool. public class InitParameters diff --git a/src/FrostFS.SDK.ClientV2/Pool/InnerPool.cs b/src/FrostFS.SDK.Client/Pool/InnerPool.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Pool/InnerPool.cs rename to src/FrostFS.SDK.Client/Pool/InnerPool.cs index 104f1f1..f712552 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/InnerPool.cs +++ b/src/FrostFS.SDK.Client/Pool/InnerPool.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Client; internal sealed class InnerPool { diff --git a/src/FrostFS.SDK.ClientV2/Pool/MethodIndex.cs b/src/FrostFS.SDK.Client/Pool/MethodIndex.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Pool/MethodIndex.cs rename to src/FrostFS.SDK.Client/Pool/MethodIndex.cs index 8a7008b..53e5430 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/MethodIndex.cs +++ b/src/FrostFS.SDK.Client/Pool/MethodIndex.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public enum MethodIndex { diff --git a/src/FrostFS.SDK.ClientV2/Pool/MethodStatus.cs b/src/FrostFS.SDK.Client/Pool/MethodStatus.cs similarity index 91% rename from src/FrostFS.SDK.ClientV2/Pool/MethodStatus.cs rename to src/FrostFS.SDK.Client/Pool/MethodStatus.cs index 33cad49..8f40f3c 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/MethodStatus.cs +++ b/src/FrostFS.SDK.Client/Pool/MethodStatus.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class MethodStatus(string name) { diff --git a/src/FrostFS.SDK.ClientV2/Pool/NodeParam.cs b/src/FrostFS.SDK.Client/Pool/NodeParam.cs similarity index 92% rename from src/FrostFS.SDK.ClientV2/Pool/NodeParam.cs rename to src/FrostFS.SDK.Client/Pool/NodeParam.cs index f95772b..92c2560 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/NodeParam.cs +++ b/src/FrostFS.SDK.Client/Pool/NodeParam.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // NodeParam groups parameters of remote node. [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] diff --git a/src/FrostFS.SDK.ClientV2/Pool/NodeStatistic.cs b/src/FrostFS.SDK.Client/Pool/NodeStatistic.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/Pool/NodeStatistic.cs rename to src/FrostFS.SDK.Client/Pool/NodeStatistic.cs index d51aa83..9323d93 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/NodeStatistic.cs +++ b/src/FrostFS.SDK.Client/Pool/NodeStatistic.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class NodeStatistic { diff --git a/src/FrostFS.SDK.ClientV2/Pool/NodesParam.cs b/src/FrostFS.SDK.Client/Pool/NodesParam.cs similarity index 88% rename from src/FrostFS.SDK.ClientV2/Pool/NodesParam.cs rename to src/FrostFS.SDK.Client/Pool/NodesParam.cs index eaeef7c..be9f012 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/NodesParam.cs +++ b/src/FrostFS.SDK.Client/Pool/NodesParam.cs @@ -1,6 +1,6 @@ using System.Collections.ObjectModel; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class NodesParam(int priority) { diff --git a/src/FrostFS.SDK.ClientV2/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs similarity index 99% rename from src/FrostFS.SDK.ClientV2/Pool/Pool.cs rename to src/FrostFS.SDK.Client/Pool/Pool.cs index 00abda2..105978d 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -8,8 +8,8 @@ using System.Threading.Tasks; using Frostfs.V2.Ape; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Interfaces; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Interfaces; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Grpc.Core; @@ -17,7 +17,7 @@ using Grpc.Core; using Microsoft.Extensions.Logging; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public partial class Pool : IFrostFSClient { diff --git a/src/FrostFS.SDK.ClientV2/Pool/RebalanceParameters.cs b/src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Pool/RebalanceParameters.cs rename to src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs index a443b2d..988002c 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/RebalanceParameters.cs +++ b/src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class RebalanceParameters( NodesParam[] nodesParams, diff --git a/src/FrostFS.SDK.ClientV2/Pool/RequestInfo.cs b/src/FrostFS.SDK.Client/Pool/RequestInfo.cs similarity index 87% rename from src/FrostFS.SDK.ClientV2/Pool/RequestInfo.cs rename to src/FrostFS.SDK.Client/Pool/RequestInfo.cs index 9eb931f..3b70650 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/RequestInfo.cs +++ b/src/FrostFS.SDK.Client/Pool/RequestInfo.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // RequestInfo groups info about pool request. struct RequestInfo diff --git a/src/FrostFS.SDK.ClientV2/Pool/Sampler.cs b/src/FrostFS.SDK.Client/Pool/Sampler.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Pool/Sampler.cs rename to src/FrostFS.SDK.Client/Pool/Sampler.cs index 0d4a1e0..275f3f0 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/Sampler.cs +++ b/src/FrostFS.SDK.Client/Pool/Sampler.cs @@ -1,6 +1,6 @@ using System; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class Sampler { diff --git a/src/FrostFS.SDK.ClientV2/Pool/SessionCache.cs b/src/FrostFS.SDK.Client/Pool/SessionCache.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Pool/SessionCache.cs rename to src/FrostFS.SDK.Client/Pool/SessionCache.cs index f65046f..829b1c9 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/SessionCache.cs +++ b/src/FrostFS.SDK.Client/Pool/SessionCache.cs @@ -1,7 +1,7 @@ using System; using System.Collections; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal struct SessionCache(ulong sessionExpirationDuration) { diff --git a/src/FrostFS.SDK.ClientV2/Pool/Statistic.cs b/src/FrostFS.SDK.Client/Pool/Statistic.cs similarity index 88% rename from src/FrostFS.SDK.ClientV2/Pool/Statistic.cs rename to src/FrostFS.SDK.Client/Pool/Statistic.cs index 58fa72d..c4977d5 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/Statistic.cs +++ b/src/FrostFS.SDK.Client/Pool/Statistic.cs @@ -1,6 +1,6 @@ using System.Collections.ObjectModel; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class Statistic { diff --git a/src/FrostFS.SDK.ClientV2/Pool/StatusSnapshot.cs b/src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs similarity index 79% rename from src/FrostFS.SDK.ClientV2/Pool/StatusSnapshot.cs rename to src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs index 9434cb9..2156f99 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/StatusSnapshot.cs +++ b/src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class StatusSnapshot() { diff --git a/src/FrostFS.SDK.ClientV2/Pool/WorkList.cs b/src/FrostFS.SDK.Client/Pool/WorkList.cs similarity index 93% rename from src/FrostFS.SDK.ClientV2/Pool/WorkList.cs rename to src/FrostFS.SDK.Client/Pool/WorkList.cs index 39551f4..7796e86 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/WorkList.cs +++ b/src/FrostFS.SDK.Client/Pool/WorkList.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class WorkList { diff --git a/src/FrostFS.SDK.ClientV2/Pool/WrapperPrm.cs b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Pool/WrapperPrm.cs rename to src/FrostFS.SDK.Client/Pool/WrapperPrm.cs index b389f68..3c23505 100644 --- a/src/FrostFS.SDK.ClientV2/Pool/WrapperPrm.cs +++ b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs @@ -5,7 +5,7 @@ using Grpc.Net.Client; using Microsoft.Extensions.Logging; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; // wrapperPrm is params to create clientWrapper. [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] diff --git a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs index b5fa42b..ba8ba2a 100644 --- a/src/FrostFS.SDK.ClientV2/Services/AccountingServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using FrostFS.Accounting; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class AccountingServiceProvider : ContextAccessor { diff --git a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index d05e636..840223d 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Frostfs.V2.Ape; using Frostfs.V2.Apemanager; -namespace FrostFS.SDK.ClientV2.Services; +namespace FrostFS.SDK.Client.Services; internal sealed class ApeManagerServiceProvider : ContextAccessor { diff --git a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index 4e00a7d..3a0379e 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -5,12 +5,12 @@ using System.Threading.Tasks; using FrostFS.Container; using FrostFS.Refs; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider { diff --git a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs similarity index 99% rename from src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs index 0241e31..18649d8 100644 --- a/src/FrostFS.SDK.ClientV2/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs @@ -7,7 +7,7 @@ using FrostFS.Netmap; using static FrostFS.Netmap.NetworkConfig.Types; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class NetmapServiceProvider : ContextAccessor { diff --git a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs similarity index 99% rename from src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 322fb3e..7737c62 100644 --- a/src/FrostFS.SDK.ClientV2/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -7,14 +7,14 @@ using System.Threading.Tasks; using FrostFS.Object; using FrostFS.Refs; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider diff --git a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs similarity index 94% rename from src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs rename to src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs index 73418d0..e9b5963 100644 --- a/src/FrostFS.SDK.ClientV2/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs @@ -1,10 +1,10 @@ using System.Threading.Tasks; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.Session; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class SessionServiceProvider : ContextAccessor { diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs b/src/FrostFS.SDK.Client/Services/Shared/ContextAccessor.cs similarity index 79% rename from src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs rename to src/FrostFS.SDK.Client/Services/Shared/ContextAccessor.cs index 70fc9c9..e127146 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/ContextAccessor.cs +++ b/src/FrostFS.SDK.Client/Services/Shared/ContextAccessor.cs @@ -1,4 +1,4 @@ -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal class ContextAccessor(ClientContext context) { diff --git a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs rename to src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs index e18de09..a8d80ac 100644 --- a/src/FrostFS.SDK.ClientV2/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal interface ISessionProvider { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs b/src/FrostFS.SDK.Client/Tools/ClientContext.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs rename to src/FrostFS.SDK.Client/Tools/ClientContext.cs index c0a1db3..5f7c72e 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ClientContext.cs +++ b/src/FrostFS.SDK.Client/Tools/ClientContext.cs @@ -6,7 +6,7 @@ using FrostFS.SDK.Cryptography; using Grpc.Net.Client; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { diff --git a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs b/src/FrostFS.SDK.Client/Tools/NetworkSettings.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs rename to src/FrostFS.SDK.Client/Tools/NetworkSettings.cs index 524f358..d8f5e12 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/NetworkSettings.cs +++ b/src/FrostFS.SDK.Client/Tools/NetworkSettings.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public class NetworkSettings { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs b/src/FrostFS.SDK.Client/Tools/ObjectReader.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs rename to src/FrostFS.SDK.Client/Tools/ObjectReader.cs index c6af0c8..7d5dbd7 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectReader.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectReader.cs @@ -6,7 +6,7 @@ using FrostFS.Object; using Grpc.Core; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class ObjectReader(AsyncServerStreamingCall call) : IObjectReader { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs b/src/FrostFS.SDK.Client/Tools/ObjectStreamer.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs rename to src/FrostFS.SDK.Client/Tools/ObjectStreamer.cs index a23c109..81a6b3b 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectStreamer.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectStreamer.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Grpc.Core; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class ObjectStreamer(AsyncClientStreamingCall call) : IDisposable { diff --git a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs rename to src/FrostFS.SDK.Client/Tools/ObjectTools.cs index 91df2ad..f20cacd 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -2,12 +2,12 @@ using System.Linq; using FrostFS.Object; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Google.Protobuf; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal static class ObjectTools { diff --git a/src/FrostFS.SDK.ClientV2/Tools/Range.cs b/src/FrostFS.SDK.Client/Tools/Range.cs similarity index 100% rename from src/FrostFS.SDK.ClientV2/Tools/Range.cs rename to src/FrostFS.SDK.Client/Tools/Range.cs diff --git a/src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs b/src/FrostFS.SDK.Client/Tools/RangeReader.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs rename to src/FrostFS.SDK.Client/Tools/RangeReader.cs index d1128c8..a92d710 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RangeReader.cs +++ b/src/FrostFS.SDK.Client/Tools/RangeReader.cs @@ -6,7 +6,7 @@ using FrostFS.Object; using Grpc.Core; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public sealed class RangeReader(AsyncServerStreamingCall call) : IObjectReader { diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs similarity index 95% rename from src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs rename to src/FrostFS.SDK.Client/Tools/RequestConstructor.cs index 6f19ad0..65d9e35 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs @@ -4,12 +4,12 @@ using System.Linq; using System.Security.Cryptography; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class RequestConstructor { diff --git a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs similarity index 98% rename from src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs rename to src/FrostFS.SDK.Client/Tools/RequestSigner.cs index f1006a4..a45c569 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -3,7 +3,7 @@ using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; @@ -14,7 +14,7 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class RequestSigner { diff --git a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs b/src/FrostFS.SDK.Client/Tools/SearchReader.cs similarity index 96% rename from src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs rename to src/FrostFS.SDK.Client/Tools/SearchReader.cs index ceed0da..c2e7607 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/SearchReader.cs +++ b/src/FrostFS.SDK.Client/Tools/SearchReader.cs @@ -9,7 +9,7 @@ using FrostFS.Refs; using Grpc.Core; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; internal sealed class SearchReader(AsyncServerStreamingCall call) : IDisposable { diff --git a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs similarity index 97% rename from src/FrostFS.SDK.ClientV2/Tools/Verifier.cs rename to src/FrostFS.SDK.Client/Tools/Verifier.cs index c87ef0a..bc8dd31 100644 --- a/src/FrostFS.SDK.ClientV2/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -2,9 +2,9 @@ using System; using System.Security.Cryptography; using FrostFS.Refs; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; @@ -15,7 +15,7 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; -namespace FrostFS.SDK.ClientV2; +namespace FrostFS.SDK.Client; public static class Verifier { diff --git a/src/FrostFS.SDK.ClientV2/Ростелеком.txt b/src/FrostFS.SDK.ClientV2/Ростелеком.txt deleted file mode 100644 index d3687ed..0000000 --- a/src/FrostFS.SDK.ClientV2/Ростелеком.txt +++ /dev/null @@ -1,8 +0,0 @@ -Для возврата денег потребуются документы: -1. Заполненное заявление -Форму заявления можно скачать с сайта -Шаблон заявления скачайте на сайте rt.ru. Внизу страницы перейдите в Договоры и соглашения -> «Бланки и заявления» -2. Скан-фото паспорта владельца договора (2, 3, 5 страница: кем и когда выдан, адрес регистрации) -3. Реквизиты счета владельца, выданные банком, на которые будет выполнен возврат -4. Чек об оплате -Сканы документов должны быть хорошо читаемые diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 59bde16..148ae78 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -6,9 +6,13 @@ enable - - true - + + true + + + + true + diff --git a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs b/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs deleted file mode 100644 index 80ba47a..0000000 --- a/src/FrostFS.SDK.ModelsV2/Client/ClientSettings.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace FrostFS.SDK; - -public class ClientSettings -{ - protected static readonly string errorTemplate = "{0} is required parameter"; - - public string Host { get; set; } = string.Empty; - - public virtual void Validate() - { - var errors = CheckFields(); - if (errors != null) - ThrowException(errors); - } - - protected List? CheckFields() - { - List? errors = null; - - if (string.IsNullOrWhiteSpace(Host)) - (errors ??= []).Add(string.Format(errorTemplate, nameof(Host))); - - return errors; - } - - protected static void ThrowException(List errors) - { - StringBuilder messages = new(); - - foreach (var error in errors) - { - messages.AppendLine(error); - } - - throw new ArgumentException(messages.ToString()); - } -} - -public class SingleOwnerClientSettings : ClientSettings -{ - public string Key { get; set; } = string.Empty; - - public override void Validate() - { - var errors = CheckFields(); - if (errors != null) - ThrowException(errors); - } - - protected List? CheckFields() - { - List? errors = base.CheckFields(); - - if (string.IsNullOrWhiteSpace(Key)) - (errors ??= []).Add(string.Format(errorTemplate, nameof(Key))); - - return errors; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs b/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs deleted file mode 100644 index 4543dcd..0000000 --- a/src/FrostFS.SDK.ModelsV2/Containers/ContainerId.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FrostFS.SDK; - -public class ContainerId(string id) -{ - public string Value { get; set; } = id; - - public override string ToString() - { - return Value; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs b/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs deleted file mode 100644 index 45d3b58..0000000 --- a/src/FrostFS.SDK.ModelsV2/Containers/FrostFsContainer.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace FrostFS.SDK; - -public class FrostFsContainer(BasicAcl basicAcl, FrostFsPlacementPolicy placementPolicy) -{ - public Guid Nonce { get; set; } = Guid.NewGuid(); - public BasicAcl BasicAcl { get; set; } = basicAcl; - public FrostFsPlacementPolicy PlacementPolicy { get; set; } = placementPolicy; - public FrostFsVersion? Version { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs b/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs deleted file mode 100644 index dd3252b..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/BasicAcl.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel; - -namespace FrostFS.SDK; - -public enum BasicAcl -{ - [Description("Not defined ACL")] - NotDefined = 0x00000000, - - [Description("Basic ACL for private container")] - Private = 0x1C8C8CCC, - - [Description("Basic ACL for public RO container")] - PublicRO = 0x1FBF8CFF, - - [Description("Basic ACL for public RW container")] - PublicRW = 0x1FBFBFFF, - - [Description("Basic ACL for public append container")] - PublicAppend = 0x1FBF9FFF, -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs deleted file mode 100644 index 1fdeb60..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectMatchType.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FrostFS.SDK; - -public enum FrostFsObjectMatchType -{ - Unspecified = 0, - Equals = 1, - NotEquals = 2, - KeyAbsent = 3, - StartsWith = 4 -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs deleted file mode 100644 index 793fe2e..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsObjectType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK; - -public enum FrostFsObjectType -{ - Regular = 0, - Tombstone = 1, - Lock = 3 -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs b/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs deleted file mode 100644 index 7eb185c..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/FrostFsStatusCode.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace FrostFS.SDK; - -public enum FrostFsStatusCode -{ - Success = 0, - Internal = 1024, - WrongMagicNumber = 1025, - SignatureVerificationFailure = 1026, - NodeUnderMaintenance = 1027, - ObjectAccessDenied = 2048, - ObjectNotFound = 2049, - ObjectLocked = 2050, - LockNotRegularObject = 2051, - ObjectAlreadyRemoved = 2052, - OutOfRange = 2053, - ContainerNotFound = 3072, - EAclNotFound = 3073, - ContainerAccessDenied = 3074, - TokenNotFound = 4096, - TokenExpired = 4097, - ApeManagerAccessDenied = 5120 -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs b/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs deleted file mode 100644 index 2821e55..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/NodeState.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FrostFS.SDK; - -public enum NodeState -{ - Unspecified = 0, - Online = 1, - Offline = 2, - Maintenance = 3 -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs b/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs deleted file mode 100644 index 4b5634c..0000000 --- a/src/FrostFS.SDK.ModelsV2/Enums/SignatureScheme.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK; - -public enum SignatureScheme -{ - EcdsaSha512, - EcdsaRfc6979Sha256, - EcdsaRfc6979Sha256WalletConnect -} diff --git a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj b/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj deleted file mode 100644 index 1b4ad74..0000000 --- a/src/FrostFS.SDK.ModelsV2/FrostFS.SDK.ModelsV2.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - netstandard2.0 - 12.0 - enable - - - - true - - - - - - - - - - - - diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs b/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs deleted file mode 100644 index 706b3f2..0000000 --- a/src/FrostFS.SDK.ModelsV2/Misc/CallStatistics.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FrostFS.SDK; - -public class CallStatistics -{ - public string? MethodName { get; set; } - public long ElapsedMicroSeconds { get; set; } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs b/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs deleted file mode 100644 index 82017f9..0000000 --- a/src/FrostFS.SDK.ModelsV2/Misc/CheckSum.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FrostFS.SDK.Cryptography; -using System; - -namespace FrostFS.SDK; - -public class CheckSum -{ - // type is always Sha256 - public byte[]? Hash { get; set; } - - public static CheckSum CreateCheckSum(byte[] content) - { - return new CheckSum { Hash = content.Sha256() }; - } - - public override string ToString() - { - return BitConverter.ToString(Hash).Replace("-", ""); - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs b/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs deleted file mode 100644 index 332a3e3..0000000 --- a/src/FrostFS.SDK.ModelsV2/Misc/Constants.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace FrostFS.SDK; - -public class Constants -{ - public const int ObjectChunkSize = 3 * (1 << 20); - public const int Sha256HashLength = 32; - - // HeaderPrefix is a prefix of key to object header value or property. - public const string HeaderPrefix = "$Object:"; - - // FilterHeaderVersion is a filter key to "version" field of the object header. - public const string FilterHeaderVersion = HeaderPrefix + "version"; - - // FilterHeaderObjectID is a filter key to "object_id" field of the object. - public const string FilterHeaderObjectID = HeaderPrefix + "objectID"; - - // FilterHeaderContainerID is a filter key to "container_id" field of the object header. - public const string FilterHeaderContainerID = HeaderPrefix + "containerID"; - - // FilterHeaderOwnerID is a filter key to "owner_id" field of the object header. - public const string FilterHeaderOwnerID = HeaderPrefix + "ownerID"; - - // FilterHeaderCreationEpoch is a filter key to "creation_epoch" field of the object header. - public const string FilterHeaderCreationEpoch = HeaderPrefix + "creationEpoch"; - - // FilterHeaderPayloadLength is a filter key to "payload_length" field of the object header. - public const string FilterHeaderPayloadLength = HeaderPrefix + "payloadLength"; - - // FilterHeaderPayloadHash is a filter key to "payload_hash" field of the object header. - public const string FilterHeaderPayloadHash = HeaderPrefix + "payloadHash"; - - // FilterHeaderObjectType is a filter key to "object_type" field of the object header. - public const string FilterHeaderObjectType = HeaderPrefix + "objectType"; - - // FilterHeaderHomomorphicHash is a filter key to "homomorphic_hash" field of the object header. - public const string FilterHeaderHomomorphicHash = HeaderPrefix + "homomorphicHash"; - - // FilterHeaderParent is a filter key to "split.parent" field of the object header. - public const string FilterHeaderParent = HeaderPrefix + "split.parent"; - - // FilterHeaderSplitID is a filter key to "split.splitID" field of the object header. - public const string FilterHeaderSplitID = HeaderPrefix + "split.splitID"; - - // FilterHeaderECParent is a filter key to "ec.parent" field of the object header. - public const string FilterHeaderECParent = HeaderPrefix + "ec.parent"; - - // FilterPropertyRoot is a filter key to check if regular object is on top of split hierarchy. - public const string FilterHeaderRoot = HeaderPrefix + "ROOT"; - - // FilterPropertyPhy is a filter key to check if an object physically stored on a node. - public const string FilterHeaderPhy = HeaderPrefix + "PHY"; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs deleted file mode 100644 index 9581b21..0000000 --- a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsNodeInfo.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace FrostFS.SDK; - -public class FrostFsNodeInfo( - FrostFsVersion version, - NodeState state, - IReadOnlyCollection addresses, - IReadOnlyDictionary attributes, - ReadOnlyMemory publicKey) -{ - public NodeState State { get; private set; } = state; - public FrostFsVersion Version { get; private set; } = version; - public IReadOnlyCollection Addresses { get; private set; } = addresses; - public IReadOnlyDictionary Attributes { get; private set; } = attributes; - public ReadOnlyMemory PublicKey { get; private set; } = publicKey; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs deleted file mode 100644 index 410598c..0000000 --- a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsPlacementPolicy.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Linq; - -namespace FrostFS.SDK; - -public class FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) : IComparable -{ - public FrostFsReplica[] Replicas { get; private set; } = replicas; - public bool Unique { get; private set; } = unique; - - public int CompareTo(FrostFsPlacementPolicy other) - { - var notEqual = other == null - || Unique != other.Unique - || Replicas.Length != other.Replicas.Length; - - if (notEqual) - return 1; - - foreach (var replica in Replicas) - { - if (!other!.Replicas.Any(r => r.Count == replica.Count && r.Selector == replica.Selector)) - return 1; - } - - return 0; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs deleted file mode 100644 index 8fd1bd9..0000000 --- a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsReplica.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace FrostFS.SDK; - -public class FrostFsReplica -{ - public int Count { get; set; } - public string Selector { get; set; } - - public FrostFsReplica(int count, string? selector = null) - { - selector ??= string.Empty; - - Count = count; - Selector = selector; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs deleted file mode 100644 index 06c3ca2..0000000 --- a/src/FrostFS.SDK.ModelsV2/Netmap/FrostFsVersion.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FrostFS.SDK; - -public class FrostFsVersion(int major, int minor) -{ - public int Major { get; set; } = major; - public int Minor { get; set; } = minor; - - public bool IsSupported(FrostFsVersion version) - { - return Major == version.Major; - } - - public override string ToString() - { - return $"v{Major}.{Minor}"; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs b/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs deleted file mode 100644 index a371564..0000000 --- a/src/FrostFS.SDK.ModelsV2/Netmap/NetmapInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace FrostFS.SDK; - -public class NetmapSnapshot(ulong epoch, IReadOnlyList nodeInfoCollection) -{ - public ulong Epoch { get; private set; } = epoch; - - public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs b/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs deleted file mode 100644 index e382ec5..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/FrostFsObject.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; - -namespace FrostFS.SDK; - -public class FrostFsObject -{ - /// - /// Creates new instance from ObjectHeader - /// - /// - public FrostFsObject(ObjectHeader header) - { - Header = header; - } - - /// - /// Creates new instance with specified parameters - /// - /// - /// - public FrostFsObject(ContainerId container, FrostFsObjectType objectType = FrostFsObjectType.Regular) - { - Header = new ObjectHeader(containerId: container, type: objectType); - } - - /// - /// Header contains metadata for the object - /// - /// - public ObjectHeader Header { get; set; } - - /// - /// The value is calculated internally as a hash of ObjectHeader. Do not use pre-calculated value is the object has been changed. - /// - public ObjectId? ObjectId - { - get; set; - } - - /// - /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings - /// Used only for PutSingleObject method - /// - /// Buffer for output data - public byte[] Payload { get; set; } = []; - - /// - /// A payload is obtained via stream reader - /// - /// Reader for received data - public IObjectReader? ObjectReader { get; set; } - - /// - /// Applied only for the last Object in chain in case of manual multipart uploading - /// - /// Parent for multipart object - public void SetParent(ObjectHeader largeObjectHeader) - { - if (Header?.Split == null) - throw new Exception("The object is not initialized properly"); - - Header.Split.ParentHeader = largeObjectHeader; - } -} - -public class LargeObject(ContainerId container) : FrostFsObject(container) -{ - public ulong PayloadLength - { - get { return Header!.PayloadLength; } - } -} - -public class LinkObject : FrostFsObject -{ - public LinkObject(ContainerId containerId, SplitId splitId, ObjectHeader largeObjectHeader) : base(containerId) - { - Header!.Split = new Split(splitId) - { - ParentHeader = largeObjectHeader - }; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs b/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs deleted file mode 100644 index 51211fd..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/IObjectReader.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace FrostFS.SDK; - -public interface IObjectReader : IDisposable -{ - Task?> ReadChunk(CancellationToken cancellationToken = default); -} diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs deleted file mode 100644 index 110db12..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FrostFS.SDK; - -public class ObjectAttribute(string key, string value) -{ - public string Key { get; set; } = key; - public string Value { get; set; } = value; -} diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs deleted file mode 100644 index 40f654d..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectFilter.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace FrostFS.SDK; - -public interface IObjectFilter -{ - public FrostFsObjectMatchType MatchType { get; set; } - public string Key { get; set; } - - string? GetSerializedValue(); -} - -public abstract class ObjectFilter(FrostFsObjectMatchType matchType, string key, T value) : IObjectFilter -{ - public FrostFsObjectMatchType MatchType { get; set; } = matchType; - public string Key { get; set; } = key; - - public T Value { get; set; } = value; - - public string? GetSerializedValue() - { - return Value?.ToString(); - } -} - -/// -/// Creates filter to search by Attribute -/// -/// Match type -/// Attribute key -/// Attribute value -public class FilterByAttribute(FrostFsObjectMatchType matchType, string key, string value) : ObjectFilter(matchType, key, value) { } - -/// -/// Creates filter to search by ObjectId -/// -/// Match type -/// ObjectId -public class FilterByObjectId(FrostFsObjectMatchType matchType, ObjectId objectId) : ObjectFilter(matchType, Constants.FilterHeaderObjectID, objectId) { } - -/// -/// Creates filter to search by OwnerId -/// -/// Match type -/// ObjectId -public class FilterByOwnerId(FrostFsObjectMatchType matchType, OwnerId ownerId) : ObjectFilter(matchType, Constants.FilterHeaderOwnerID, ownerId) { } - -/// -/// Creates filter to search by Version -/// -/// Match type -/// Version -public class FilterByVersion(FrostFsObjectMatchType matchType, FrostFsVersion version) : ObjectFilter(matchType, Constants.FilterHeaderVersion, version) { } - -/// -/// Creates filter to search by ContainerId -/// -/// Match type -/// ContainerId -public class FilterByContainerId(FrostFsObjectMatchType matchType, ContainerId containerId) : ObjectFilter(matchType, Constants.FilterHeaderContainerID, containerId) { } - -/// -/// Creates filter to search by creation Epoch -/// -/// Match type -/// Creation Epoch -public class FilterByEpoch(FrostFsObjectMatchType matchType, ulong epoch) : ObjectFilter(matchType, Constants.FilterHeaderCreationEpoch, epoch) { } - -/// -/// Creates filter to search by Payload Length -/// -/// Match type -/// Payload Length -public class FilterByPayloadLength(FrostFsObjectMatchType matchType, ulong payloadLength) : ObjectFilter(matchType, Constants.FilterHeaderPayloadLength, payloadLength) { } - -/// -/// Creates filter to search by Payload Hash -/// -/// Match type -/// Payload Hash -public class FilterByPayloadHash(FrostFsObjectMatchType matchType, CheckSum payloadHash) : ObjectFilter(matchType, Constants.FilterHeaderPayloadHash, payloadHash) { } - -/// -/// Creates filter to search by Parent -/// -/// Match type -/// Parent -public class FilterByParent(FrostFsObjectMatchType matchType, ObjectId parentId) : ObjectFilter(matchType, Constants.FilterHeaderParent, parentId) { } - -/// -/// Creates filter to search by SplitId -/// -/// Match type -/// SplitId -public class FilterBySplitId(FrostFsObjectMatchType matchType, SplitId splitId) : ObjectFilter(matchType, Constants.FilterHeaderSplitID, splitId) { } - -/// -/// Creates filter to search by Payload Hash -/// -/// Match type -/// Payload Hash -public class FilterByECParent(FrostFsObjectMatchType matchType, ObjectId ecParentId) : ObjectFilter(matchType, Constants.FilterHeaderECParent, ecParentId) { } - -/// -/// Creates filter to search Root objects -/// -public class FilterByRootObject() : ObjectFilter(FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderRoot, string.Empty) { } - -/// -/// Creates filter to search objects that are physically stored on the server -/// (FrostFsObjectMatchType.Unspecified, Constants.FilterHeaderPhy, string.Empty) { } - diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs deleted file mode 100644 index 3e3ecd3..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectHeader.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; - -namespace FrostFS.SDK; - -public class ObjectHeader( - ContainerId containerId, - FrostFsObjectType type = FrostFsObjectType.Regular, - params ObjectAttribute[] attributes - ) -{ - public OwnerId? OwnerId { get; set; } - - public List Attributes { get; set; } = [.. attributes]; - - public ContainerId ContainerId { get; set; } = containerId; - - public ulong PayloadLength { get; set; } - - public byte[]? PayloadCheckSum { get; set; } - - public FrostFsObjectType ObjectType { get; set; } = type; - - public FrostFsVersion? Version { get; set; } - - public Split? Split { get; set; } -} diff --git a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs b/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs deleted file mode 100644 index 9072361..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/ObjectId.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK; - -public class ObjectId(string id) -{ - public string Value { get; } = id; - - public static ObjectId FromHash(byte[] hash) - { - if (hash.Length != Constants.Sha256HashLength) - throw new FormatException("ObjectID must be a sha256 hash."); - - return new ObjectId(Base58.Encode(hash)); - } - - public byte[] ToHash() - { - return Base58.Decode(Value); - } - - public override string ToString() - { - return Value; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs b/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs deleted file mode 100644 index 5da87ef..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/OwnerId.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Security.Cryptography; - -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK; - -public class OwnerId(string id) -{ - public string Value { get; } = id; - - public static OwnerId FromKey(ECDsa key) - { - return new OwnerId(key.PublicKey().PublicKeyToAddress()); - } - - public byte[] ToHash() - { - return Base58.Decode(Value); - } - - public override string ToString() - { - return Value; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs b/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs deleted file mode 100644 index 8af45c1..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/SplitId.cs +++ /dev/null @@ -1,62 +0,0 @@ -using FrostFS.SDK.Cryptography; - -using Google.Protobuf; - -using System; - -namespace FrostFS.SDK; - -public class SplitId -{ - private readonly Guid id; - - ByteString? _message; - - public SplitId() - { - id = Guid.NewGuid(); - } - - public SplitId(Guid guid) - { - id = guid; - } - - private SplitId(byte[] binary) - { - id = new Guid(binary); - } - - private SplitId(string str) - { - id = new Guid(str); - } - - public static SplitId CreateFromBinary(byte[] binaryData) - { - return new SplitId(binaryData); - } - - public static SplitId CreateFromString(string stringData) - { - return new SplitId(stringData); - } - - public override string ToString() - { - return id.ToString(); - } - - public byte[]? ToBinary() - { - if (id == Guid.Empty) - return null; - - return id.ToBytes(); - } - - public ByteString? GetMessage() - { - return _message ??= ByteString.CopyFrom(ToBinary()); - } -} diff --git a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs b/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs deleted file mode 100644 index 64b3515..0000000 --- a/src/FrostFS.SDK.ModelsV2/Object/Splitter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; - -namespace FrostFS.SDK; - -public class Split(SplitId splitId) -{ - public Split() : this(new SplitId()) - { - } - - public SplitId SplitId { get; private set; } = splitId; - - public ObjectId? Parent { get; set; } - - public ObjectId? Previous { get; set; } - - public FrostFsSignature? ParentSignature { get; set; } - - public ObjectHeader? ParentHeader { get; set; } - - public List Children { get; } = []; - - public Refs.Signature ParentSignatureGrpc { get; set; } -} diff --git a/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs deleted file mode 100644 index b5e5831..0000000 --- a/src/FrostFS.SDK.ModelsV2/Response/FrostFsResponseStatus.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace FrostFS.SDK; - -public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = null) -{ - public FrostFsStatusCode Code { get; set; } = code; - public string Message { get; set; } = message ?? string.Empty; - - public bool IsSuccess => Code == FrostFsStatusCode.Success; - - public override string ToString() - { - return $"Response status: {Code}. Message: {Message}."; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs b/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs deleted file mode 100644 index d7ca6b6..0000000 --- a/src/FrostFS.SDK.ModelsV2/Response/FrostFsSignature.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FrostFS.SDK; - -public class FrostFsSignature -{ - public byte[]? Key { get; set; } - - public byte[]? Sign { get; set; } - - public SignatureScheme Scheme { get; set; } -} diff --git a/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs b/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs deleted file mode 100644 index 36dad09..0000000 --- a/src/FrostFS.SDK.ModelsV2/Response/MetaHeader.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace FrostFS.SDK; - -public class MetaHeader(FrostFsVersion version, int epoch, int ttl) -{ - public FrostFsVersion Version { get; set; } = version; - public int Epoch { get; set; } = epoch; - public int Ttl { get; set; } = ttl; - - public static MetaHeader Default() - { - return new MetaHeader( - new FrostFsVersion( - major: 2, - minor: 13 - ), - epoch: 0, - ttl: 2 - ); - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs deleted file mode 100644 index 8954780..0000000 --- a/src/FrostFS.SDK.ModelsV2/Session/FrostFsSessionToken.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FrostFS.SDK; - -public class FrostFsSessionToken(byte[] token) -{ - public byte[] Token { get; private set; } = token; -} diff --git a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj similarity index 88% rename from src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj rename to src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 093351c..0bf48d5 100644 --- a/src/FrostFS.SDK.ProtosV2/FrostFS.SDK.ProtosV2.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -6,9 +6,13 @@ enable - - true - + + true + + + + true + diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IMetaHeader.cs b/src/FrostFS.SDK.Protos/Interfaces/IMetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/Interfaces/IMetaHeader.cs rename to src/FrostFS.SDK.Protos/Interfaces/IMetaHeader.cs diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs b/src/FrostFS.SDK.Protos/Interfaces/IRequest.cs similarity index 80% rename from src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs rename to src/FrostFS.SDK.Protos/Interfaces/IRequest.cs index 42129c3..a3a269f 100644 --- a/src/FrostFS.SDK.ProtosV2/Interfaces/IRequest.cs +++ b/src/FrostFS.SDK.Protos/Interfaces/IRequest.cs @@ -1,6 +1,6 @@ using FrostFS.Session; -namespace FrostFS.SDK.ProtosV2.Interfaces; +namespace FrostFS.SDK.Proto.Interfaces; public interface IRequest : IVerifiableMessage { diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs b/src/FrostFS.SDK.Protos/Interfaces/IResponse.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/Interfaces/IResponse.cs rename to src/FrostFS.SDK.Protos/Interfaces/IResponse.cs diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs b/src/FrostFS.SDK.Protos/Interfaces/IVerifiableMessage.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/Interfaces/IVerifiableMessage.cs rename to src/FrostFS.SDK.Protos/Interfaces/IVerifiableMessage.cs diff --git a/src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs b/src/FrostFS.SDK.Protos/Interfaces/IVerificationHeader.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/Interfaces/IVerificationHeader.cs rename to src/FrostFS.SDK.Protos/Interfaces/IVerificationHeader.cs diff --git a/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs b/src/FrostFS.SDK.Protos/accounting/Extension.Message.cs similarity index 97% rename from src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs rename to src/FrostFS.SDK.Protos/accounting/Extension.Message.cs index af77424..82b7c78 100644 --- a/src/FrostFS.SDK.ProtosV2/accounting/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/accounting/Extension.Message.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/accounting/service.proto b/src/FrostFS.SDK.Protos/accounting/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/accounting/service.proto rename to src/FrostFS.SDK.Protos/accounting/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/accounting/types.proto b/src/FrostFS.SDK.Protos/accounting/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/accounting/types.proto rename to src/FrostFS.SDK.Protos/accounting/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/acl/types.proto b/src/FrostFS.SDK.Protos/acl/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/acl/types.proto rename to src/FrostFS.SDK.Protos/acl/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/ape/types.proto b/src/FrostFS.SDK.Protos/ape/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/ape/types.proto rename to src/FrostFS.SDK.Protos/ape/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs b/src/FrostFS.SDK.Protos/apemanager/Extension.Message.cs similarity index 98% rename from src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs rename to src/FrostFS.SDK.Protos/apemanager/Extension.Message.cs index 408622d..53b7fdb 100644 --- a/src/FrostFS.SDK.ProtosV2/apemanager/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/apemanager/Extension.Message.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/apemanager/service.proto b/src/FrostFS.SDK.Protos/apemanager/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/apemanager/service.proto rename to src/FrostFS.SDK.Protos/apemanager/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs b/src/FrostFS.SDK.Protos/container/Extension.Message.cs similarity index 99% rename from src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs rename to src/FrostFS.SDK.Protos/container/Extension.Message.cs index 010072e..7cb69a8 100644 --- a/src/FrostFS.SDK.ProtosV2/container/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/container/Extension.Message.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/container/service.proto b/src/FrostFS.SDK.Protos/container/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/container/service.proto rename to src/FrostFS.SDK.Protos/container/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/container/types.proto b/src/FrostFS.SDK.Protos/container/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/container/types.proto rename to src/FrostFS.SDK.Protos/container/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/lock/types.proto b/src/FrostFS.SDK.Protos/lock/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/lock/types.proto rename to src/FrostFS.SDK.Protos/lock/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs b/src/FrostFS.SDK.Protos/netmap/Extension.Message.cs similarity index 98% rename from src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs rename to src/FrostFS.SDK.Protos/netmap/Extension.Message.cs index 1001bce..8579baf 100644 --- a/src/FrostFS.SDK.ProtosV2/netmap/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/netmap/Extension.Message.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/netmap/service.proto b/src/FrostFS.SDK.Protos/netmap/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/netmap/service.proto rename to src/FrostFS.SDK.Protos/netmap/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/netmap/types.proto b/src/FrostFS.SDK.Protos/netmap/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/netmap/types.proto rename to src/FrostFS.SDK.Protos/netmap/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs b/src/FrostFS.SDK.Protos/object/Extension.Message.cs similarity index 99% rename from src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs rename to src/FrostFS.SDK.Protos/object/Extension.Message.cs index 6691400..f9348ff 100644 --- a/src/FrostFS.SDK.ProtosV2/object/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/object/Extension.Message.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/object/service.proto b/src/FrostFS.SDK.Protos/object/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/object/service.proto rename to src/FrostFS.SDK.Protos/object/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/object/types.proto b/src/FrostFS.SDK.Protos/object/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/object/types.proto rename to src/FrostFS.SDK.Protos/object/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/refs/types.proto b/src/FrostFS.SDK.Protos/refs/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/refs/types.proto rename to src/FrostFS.SDK.Protos/refs/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs b/src/FrostFS.SDK.Protos/session/Extension.Message.cs similarity index 96% rename from src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs rename to src/FrostFS.SDK.Protos/session/Extension.Message.cs index 4f0850b..b7ca64e 100644 --- a/src/FrostFS.SDK.ProtosV2/session/Extension.Message.cs +++ b/src/FrostFS.SDK.Protos/session/Extension.Message.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using Google.Protobuf; diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.MetaHeader.cs b/src/FrostFS.SDK.Protos/session/Extension.MetaHeader.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/session/Extension.MetaHeader.cs rename to src/FrostFS.SDK.Protos/session/Extension.MetaHeader.cs diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.VerificationHeader.cs b/src/FrostFS.SDK.Protos/session/Extension.VerificationHeader.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/session/Extension.VerificationHeader.cs rename to src/FrostFS.SDK.Protos/session/Extension.VerificationHeader.cs diff --git a/src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs b/src/FrostFS.SDK.Protos/session/Extension.XHeader.cs similarity index 100% rename from src/FrostFS.SDK.ProtosV2/session/Extension.XHeader.cs rename to src/FrostFS.SDK.Protos/session/Extension.XHeader.cs diff --git a/src/FrostFS.SDK.ProtosV2/session/service.proto b/src/FrostFS.SDK.Protos/session/service.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/session/service.proto rename to src/FrostFS.SDK.Protos/session/service.proto diff --git a/src/FrostFS.SDK.ProtosV2/session/types.proto b/src/FrostFS.SDK.Protos/session/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/session/types.proto rename to src/FrostFS.SDK.Protos/session/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/status/types.proto b/src/FrostFS.SDK.Protos/status/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/status/types.proto rename to src/FrostFS.SDK.Protos/status/types.proto diff --git a/src/FrostFS.SDK.ProtosV2/tombstone/types.proto b/src/FrostFS.SDK.Protos/tombstone/types.proto similarity index 100% rename from src/FrostFS.SDK.ProtosV2/tombstone/types.proto rename to src/FrostFS.SDK.Protos/tombstone/types.proto diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/ContainerTest.cs index 8f4e868..0d12107 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/ContainerTest.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Google.Protobuf; diff --git a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs index f9bfc9a..55f548d 100644 --- a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs +++ b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs @@ -1,4 +1,4 @@ -using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Client.Interfaces; using Microsoft.Extensions.Options; @@ -29,7 +29,7 @@ public abstract class ContainerTestsBase protected IFrostFSClient GetClient() { - return ClientV2.FrostFSClient.GetTestInstance( + return Client.FrostFSClient.GetTestInstance( Settings, null, new NetworkMocker(this.key).GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj index 2b74f0f..d5cb78f 100644 --- a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -9,6 +9,14 @@ true + + true + + + + true + + @@ -22,11 +30,11 @@ - + - + diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs index e60b600..d1366c7 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs @@ -1,8 +1,8 @@ using System.Security.Cryptography; using FrostFS.Object; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 63e3d64..c09df83 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -1,8 +1,8 @@ using System.Security.Cryptography; using FrostFS.Object; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs index f386468..7fba4f6 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ClientStreamWriter.cs @@ -1,6 +1,6 @@ using System.Collections.ObjectModel; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using Grpc.Core; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index e727cfb..ca1c670 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -2,8 +2,8 @@ using System.Security.Cryptography; using FrostFS.Container; using FrostFS.Object; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index aa4d048..0ad6ab0 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -2,8 +2,8 @@ using System.Collections.ObjectModel; using FrostFS.Container; using FrostFS.Refs; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 7c44fc8..4b2b5c1 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -2,8 +2,8 @@ using System.Collections.ObjectModel; using System.Security.Cryptography; using FrostFS.Object; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; diff --git a/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs b/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs index a2672c8..7eb53bc 100644 --- a/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs +++ b/src/FrostFS.SDK.Tests/Mocks/PatchStreamWriter.cs @@ -1,6 +1,6 @@ using System.Collections.ObjectModel; -using FrostFS.SDK.ProtosV2.Interfaces; +using FrostFS.SDK.Proto.Interfaces; using Grpc.Core; diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 99fa2b2..2123460 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using FrostFS.Netmap; -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Client; using Google.Protobuf; diff --git a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs index 071b425..8090ecd 100644 --- a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs +++ b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; @@ -37,7 +37,7 @@ public abstract class NetworkTestsBase protected IFrostFSClient GetClient() { - return ClientV2.FrostFSClient.GetTestInstance( + return Client.FrostFSClient.GetTestInstance( Settings, null, Mocker.GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index 3f3a404..dabbbc8 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -4,8 +4,8 @@ using System.Security.Cryptography; using System.Text; using FrostFS.Refs; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using Google.Protobuf; diff --git a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs index 543bda6..ba68e6f 100644 --- a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs @@ -1,5 +1,5 @@ -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs index 01320b5..efc4802 100644 --- a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index 8009f5c..4cf03ba 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Mappers.GRPC; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK.Tests; diff --git a/src/FrostFS.SDK.Tests/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/SessionTestsBase.cs index a8fbdaf..93bc140 100644 --- a/src/FrostFS.SDK.Tests/SessionTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SessionTestsBase.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; @@ -37,7 +37,7 @@ public abstract class SessionTestsBase protected IFrostFSClient GetClient() { - return ClientV2.FrostFSClient.GetTestInstance( + return Client.FrostFSClient.GetTestInstance( Settings, null, new NetworkMocker(this.key).GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs index 56acfc3..ba822c7 100644 --- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using FrostFS.SDK.ClientV2; -using FrostFS.SDK.ClientV2.Interfaces; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index e0d53d7..5d67029 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -using FrostFS.SDK.ClientV2; +using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.SmokeTests; From 749000a090b70bbfd8f18a45c4dd0e8f3c767b78 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 18 Nov 2024 16:57:20 +0300 Subject: [PATCH 31/65] [#28] Client: Apply code optimizations Signed-off-by: Pavel Gross --- .editorconfig | 4 +- src/FrostFS.SDK.Client/CllientKey.cs | 4 + .../FrostFS.SDK.Client.csproj | 4 + src/FrostFS.SDK.Client/FrostFSClient.cs | 340 +++++++++--------- .../Interfaces/IFrostFSClient.cs | 4 +- src/FrostFS.SDK.Client/Mappers/ContainerId.cs | 2 +- .../Mappers/Netmap/NodeInfo.cs | 2 +- .../Mappers/Object/Object.cs | 2 +- .../Mappers/Object/ObjectHeaderMapper.cs | 2 +- .../Mappers/Object/ObjectId.cs | 2 +- src/FrostFS.SDK.Client/Mappers/OwnerId.cs | 2 +- .../Models/Client/ClientSettings.cs | 63 +--- .../Models/Containers/FrostFsContainerId.cs | 2 +- .../Models/Containers/FrostFsContainerInfo.cs | 2 +- .../Models/Netmap/FrostFsVersion.cs | 2 +- .../Models/Object/FrostFsObjectId.cs | 7 +- .../Models/Object/FrostFsOwner.cs | 1 + .../Models/Session/FrostFsSessionToken.cs | 114 +++++- .../Parameters/CallContext.cs | 31 +- src/FrostFS.SDK.Client/Pool/ClientWrapper.cs | 3 +- src/FrostFS.SDK.Client/Pool/InitParameters.cs | 6 + src/FrostFS.SDK.Client/Pool/Pool.cs | 194 +--------- src/FrostFS.SDK.Client/Pool/SessionCache.cs | 38 +- src/FrostFS.SDK.Client/Pool/WrapperPrm.cs | 9 +- .../Services/AccountingServiceProvider.cs | 4 +- .../Services/ApeManagerServiceProvider.cs | 22 +- .../Services/ContainerServiceProvider.cs | 84 ++--- .../Services/NetmapServiceProvider.cs | 87 +++-- .../Services/ObjectServiceProvider.cs | 234 +++++------- .../Services/SessionServiceProvider.cs | 8 +- .../Services/Shared/SessionProvider.cs | 15 +- src/FrostFS.SDK.Client/Tools/ClientContext.cs | 34 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 20 +- .../Tools/RequestConstructor.cs | 58 --- src/FrostFS.SDK.Client/Tools/RequestSigner.cs | 39 +- src/FrostFS.SDK.Client/Tools/SearchReader.cs | 10 +- src/FrostFS.SDK.Client/Tools/Verifier.cs | 1 + .../FrostFS.SDK.Cryptography.csproj | 8 +- src/FrostFS.SDK.Cryptography/UUID.cs | 6 +- .../FrostFS.SDK.Protos.csproj | 6 +- src/FrostFS.SDK.Tests/ContainerTestsBase.cs | 4 +- .../FrostFS.SDK.Tests.csproj | 14 +- .../Mocks/AsyncStreamRangeReaderMock.cs | 5 - .../Mocks/AsyncStreamReaderMock.cs | 2 +- .../ContainerServiceBase.cs | 6 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 9 +- src/FrostFS.SDK.Tests/Mocks/SessionMock.cs | 2 +- src/FrostFS.SDK.Tests/NetworkTest.cs | 23 +- src/FrostFS.SDK.Tests/NetworkTestsBase.cs | 23 +- src/FrostFS.SDK.Tests/ObjectTest.cs | 24 +- src/FrostFS.SDK.Tests/ObjectTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/PoolSmokeTests.cs | 71 ++-- src/FrostFS.SDK.Tests/SessionTests.cs | 24 +- src/FrostFS.SDK.Tests/SessionTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/SmokeClientTests.cs | 266 ++++++-------- src/FrostFS.SDK.Tests/SmokeTestsBase.cs | 4 - src/FrostFS.SDK.Tests/{TestData => }/cat.jpg | Bin 57 files changed, 845 insertions(+), 1116 deletions(-) rename src/FrostFS.SDK.Tests/{TestData => }/cat.jpg (100%) diff --git a/.editorconfig b/.editorconfig index 7a29e50..482baac 100644 --- a/.editorconfig +++ b/.editorconfig @@ -214,7 +214,7 @@ dotnet_diagnostic.CA1700.severity = warning dotnet_diagnostic.CA1707.severity = warning # CA1708: Identifiers should differ by more than case -dotnet_diagnostic.CA1708.severity = warning +dotnet_diagnostic.CA1708.severity = none # CA1710: Identifiers should have correct suffix dotnet_diagnostic.CA1710.severity = warning @@ -232,7 +232,7 @@ dotnet_diagnostic.CA1713.severity = warning dotnet_diagnostic.CA1715.severity = warning # CA1716: Identifiers should not match keywords -dotnet_diagnostic.CA1716.severity = warning +dotnet_diagnostic.CA1716.severity = none # CA1720: Identifier contains type name dotnet_diagnostic.CA1720.severity = warning diff --git a/src/FrostFS.SDK.Client/CllientKey.cs b/src/FrostFS.SDK.Client/CllientKey.cs index cdafb3f..da02b02 100644 --- a/src/FrostFS.SDK.Client/CllientKey.cs +++ b/src/FrostFS.SDK.Client/CllientKey.cs @@ -11,4 +11,8 @@ public class ClientKey(ECDsa key) internal ECDsa ECDsaKey { get; } = key; internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey()); + + internal string PublicKey { get; } = key.PublicKey().ToString(); + + internal FrostFsOwner Owner { get; } = new FrostFsOwner(key.PublicKey().PublicKeyToAddress()); } diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index e175d13..7f4df50 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -14,6 +14,10 @@ true + + + true + diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 4112a5b..e73ba49 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -4,12 +4,7 @@ using System.Net.Http; using System.Threading.Tasks; using Frostfs.V2.Ape; -using Frostfs.V2.Apemanager; -using FrostFS.Accounting; -using FrostFS.Container; -using FrostFS.Netmap; -using FrostFS.Object; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Client.Services; using FrostFS.SDK.Cryptography; @@ -21,32 +16,40 @@ using Grpc.Net.Client; using Microsoft.Extensions.Options; +using static Frostfs.V2.Apemanager.APEManagerService; +using static FrostFS.Accounting.AccountingService; +using static FrostFS.Container.ContainerService; +using static FrostFS.Netmap.NetmapService; +using static FrostFS.Object.ObjectService; +using static FrostFS.Session.SessionService; + namespace FrostFS.SDK.Client; public class FrostFSClient : IFrostFSClient { private bool isDisposed; - internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; } + internal ContainerServiceClient? ContainerServiceClient { get; set; } + internal ContainerServiceProvider? ContainerServiceProvider { get; set; } - internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; } + internal NetmapServiceClient? NetmapServiceClient { get; set; } + internal NetmapServiceProvider? NetmapServiceProvider { get; set; } - internal APEManagerService.APEManagerServiceClient? ApeManagerServiceClient { get; set; } + internal APEManagerServiceClient? ApeManagerServiceClient { get; set; } + internal ApeManagerServiceProvider? ApeManagerServiceProvider { get; set; } - internal SessionService.SessionServiceClient? SessionServiceClient { get; set; } + internal SessionServiceClient? SessionServiceClient { get; set; } + internal SessionServiceProvider? SessionServiceProvider { get; set; } - internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; } + internal ObjectServiceClient? ObjectServiceClient { get; set; } + internal ObjectServiceProvider? ObjectServiceProvider { get; set; } - internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; } + internal AccountingServiceClient? AccountingServiceClient { get; set; } + internal AccountingServiceProvider? AccountingServiceProvider { get; set; } internal ClientContext ClientCtx { get; set; } - public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) - { - return new FrostFSClient(clientOptions, channelOptions); - } - - public static IFrostFSClient GetSingleOwnerInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) + public static IFrostFSClient GetSingleOwnerInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { return new FrostFSClient(clientOptions, channelOptions); } @@ -62,28 +65,28 @@ public class FrostFSClient : IFrostFSClient /// Object.ObjectService.ObjectServiceClient implementation /// public static IFrostFSClient GetTestInstance( - IOptions clientOptions, + IOptions settings, GrpcChannelOptions? channelOptions, - NetmapService.NetmapServiceClient netmapService, - SessionService.SessionServiceClient sessionService, - ContainerService.ContainerServiceClient containerService, - ObjectService.ObjectServiceClient objectService) + NetmapServiceClient netmapService, + SessionServiceClient sessionService, + ContainerServiceClient containerService, + ObjectServiceClient objectService) { - if (clientOptions is null) + if (settings is null) { - throw new ArgumentNullException(nameof(clientOptions)); + throw new ArgumentNullException(nameof(settings)); } - return new FrostFSClient(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService); + return new FrostFSClient(settings, channelOptions, containerService, netmapService, sessionService, objectService); } private FrostFSClient( - IOptions settings, + IOptions settings, GrpcChannelOptions? channelOptions, - ContainerService.ContainerServiceClient containerService, - NetmapService.NetmapServiceClient netmapService, - SessionService.SessionServiceClient sessionService, - ObjectService.ObjectServiceClient objectService) + ContainerServiceClient containerService, + NetmapServiceClient netmapService, + SessionServiceClient sessionService, + ObjectServiceClient objectService) { if (settings is null) { @@ -91,14 +94,18 @@ public class FrostFSClient : IFrostFSClient } var ecdsaKey = settings.Value.Key.LoadWif(); - FrostFsOwner.FromKey(ecdsaKey); ClientCtx = new ClientContext( client: this, - key: ecdsaKey, + key: new ClientKey(ecdsaKey), owner: FrostFsOwner.FromKey(ecdsaKey), channel: InitGrpcChannel(settings.Value.Host, channelOptions), - version: new FrostFsVersion(2, 13)); + version: new FrostFsVersion(2, 13)) + { + SessionCache = new SessionCache(0), + Callback = settings.Value.Callback, + Interceptors = settings.Value.Interceptors + }; ContainerServiceClient = containerService ?? throw new ArgumentNullException(nameof(containerService)); NetmapServiceClient = netmapService ?? throw new ArgumentNullException(nameof(netmapService)); @@ -106,28 +113,9 @@ public class FrostFSClient : IFrostFSClient ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService)); } - private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) + private FrostFSClient(IOptions settings, GrpcChannelOptions? channelOptions) { - var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized"); - - clientSettings.Validate(); - - var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - - ClientCtx = new ClientContext( - this, - key: null, - owner: null, - channel: channel, - version: new FrostFsVersion(2, 13)); - - // TODO: define timeout logic - // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); - } - - private FrostFSClient(IOptions options, GrpcChannelOptions? channelOptions) - { - var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized"); + var clientSettings = (settings?.Value) ?? throw new ArgumentNullException(nameof(settings), "Options value must be initialized"); clientSettings.Validate(); @@ -137,10 +125,15 @@ public class FrostFSClient : IFrostFSClient ClientCtx = new ClientContext( this, - key: ecdsaKey, + key: new ClientKey(ecdsaKey), owner: FrostFsOwner.FromKey(ecdsaKey), channel: channel, - version: new FrostFsVersion(2, 13)); + version: new FrostFsVersion(2, 13)) + { + SessionCache = new SessionCache(0), + Callback = settings.Value.Callback, + Interceptors = settings.Value.Interceptors + }; // TODO: define timeout logic // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); @@ -150,12 +143,14 @@ public class FrostFSClient : IFrostFSClient { ClientCtx = new ClientContext( client: this, - key: prm.Key, + key: new ClientKey(prm.Key), owner: FrostFsOwner.FromKey(prm.Key!), channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions), version: new FrostFsVersion(2, 13)) { - SessionCache = cache + SessionCache = cache, + Interceptors = prm.Interceptors, + Callback = prm.Callback }; } @@ -175,14 +170,14 @@ public class FrostFSClient : IFrostFSClient } #region ApeManagerImplementation - public Task AddChainAsync(PrmApeChainAdd args) + public Task> AddChainAsync(PrmApeChainAdd args) { if (args is null) { throw new ArgumentNullException(nameof(args)); } - var service = GetApeManagerService(args); + var service = GetApeManagerService(); return service.AddChainAsync(args); } @@ -193,7 +188,7 @@ public class FrostFSClient : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var service = GetApeManagerService(args); + var service = GetApeManagerService(); return service.RemoveChainAsync(args); } @@ -202,7 +197,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetApeManagerService(args); + var service = GetApeManagerService(); return service.ListChainAsync(args); } #endregion @@ -213,14 +208,14 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetContainerService(args); + var service = GetContainerService(); return service.GetContainerAsync(args); } public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { args ??= new PrmContainerGetAll(); - var service = GetContainerService(args); + var service = GetContainerService(); return service.ListContainersAsync(args); } @@ -229,7 +224,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetContainerService(args); + var service = GetContainerService(); return service.CreateContainerAsync(args); } @@ -238,7 +233,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetContainerService(args); + var service = GetContainerService(); return service.DeleteContainerAsync(args); } #endregion @@ -247,21 +242,21 @@ public class FrostFSClient : IFrostFSClient public Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args) { args ??= new PrmNetmapSnapshot(); - var service = GetNetmapService(args); + var service = GetNetmapService(); return service.GetNetmapSnapshotAsync(args); } public Task GetNodeInfoAsync(PrmNodeInfo? args) { args ??= new PrmNodeInfo(); - var service = GetNetmapService(args); + var service = GetNetmapService(); return service.GetLocalNodeInfoAsync(args); } public Task GetNetworkSettingsAsync(PrmNetworkSettings? args) { args ??= new PrmNetworkSettings(); - var service = GetNetmapService(args); + var service = GetNetmapService(); return service.GetNetworkSettingsAsync(args.Context!); } #endregion @@ -272,7 +267,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.GetObjectHeadAsync(args); } @@ -281,7 +276,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.GetObjectAsync(args); } @@ -290,7 +285,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.GetRangeAsync(args); } @@ -299,17 +294,16 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.GetRangeHashAsync(args); } - public Task PutObjectAsync(PrmObjectPut args) { if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.PutObjectAsync(args); } @@ -318,7 +312,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.PutSingleObjectAsync(args); } @@ -329,7 +323,7 @@ public class FrostFSClient : IFrostFSClient throw new ArgumentNullException(nameof(args)); } - var service = GetObjectService(args); + var service = GetObjectService(); return service.PatchObjectAsync(args); } @@ -338,7 +332,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.DeleteObjectAsync(args); } @@ -347,7 +341,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetObjectService(args); + var service = GetObjectService(); return service.SearchObjectsAsync(args); } #endregion @@ -358,10 +352,9 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var session = await CreateSessionInternalAsync(args).ConfigureAwait(false); + var token = await CreateSessionInternalAsync(args).ConfigureAwait(false); - var token = session.Serialize(); - return new FrostFsSessionToken(token, session.Body.Id.ToUuid()); + return new FrostFsSessionToken(token); } internal Task CreateSessionInternalAsync(PrmSessionCreate args) @@ -369,7 +362,7 @@ public class FrostFSClient : IFrostFSClient if (args is null) throw new ArgumentNullException(nameof(args)); - var service = GetSessionService(args); + var service = GetSessionService(); return service.CreateSessionAsync(args); } #endregion @@ -379,18 +372,18 @@ public class FrostFSClient : IFrostFSClient { args ??= new PrmBalance(); - var service = GetAccouningService(args); + var service = GetAccouningService(); return await service.GetBallance(args).ConfigureAwait(false); } #endregion #region ToolsImplementation - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) { if (header == null) throw new ArgumentNullException(nameof(header)); - return ObjectTools.CalculateObjectId(header, ctx); + return ObjectTools.CalculateObjectId(header, this.ClientCtx); } #endregion @@ -398,59 +391,34 @@ public class FrostFSClient : IFrostFSClient { var args = new PrmNodeInfo(ctx); - if (ctx?.Version == null) - throw new ArgumentNullException(nameof(ctx), "Version must be initialized"); - - var service = GetNetmapService(args); + var service = GetNetmapService(); var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false); - if (!localNodeInfo.Version.IsSupported(ctx.Version)) + if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; throw new FrostFsException(msg); } } - private CallInvoker? SetupClientContext(IContext ctx) + private CallInvoker? CreateInvoker() { if (isDisposed) throw new FrostFsInvalidObjectException("Client is disposed."); - if (ctx.Context!.Key == null) - { - if (ClientCtx.Key == null) - { - throw new ArgumentNullException(nameof(ctx), "Key is not initialized."); - } - - ctx.Context.Key = ClientCtx.Key.ECDsaKey; - } - - if (ctx.Context.OwnerId == null) - { - ctx.Context.OwnerId = ClientCtx.Owner ?? FrostFsOwner.FromKey(ctx.Context.Key); - } - - if (ctx.Context.Version == null) - { - if (ClientCtx.Version == null) - { - throw new ArgumentNullException(nameof(ctx), "Version is not initialized."); - } - - ctx.Context.Version = ClientCtx.Version; - } - CallInvoker? callInvoker = null; - foreach (var interceptor in ctx.Context.Interceptors) - callInvoker = AddInvoker(callInvoker, interceptor); + if (ClientCtx.Interceptors != null) + { + foreach (var interceptor in ClientCtx.Interceptors) + callInvoker = AddInvoker(callInvoker, interceptor); + } - if (ctx.Context.Callback != null) - callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback)); + if (ClientCtx.Callback != null) + callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ClientCtx.Callback)); - if (ctx.Context.PoolErrorHandler != null) - callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler)); + if (ClientCtx.PoolErrorHandler != null) + callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ClientCtx.PoolErrorHandler)); return callInvoker; @@ -465,75 +433,109 @@ public class FrostFSClient : IFrostFSClient } } - private NetmapServiceProvider GetNetmapService(IContext ctx) + private NetmapServiceProvider GetNetmapService() { - var callInvoker = SetupClientContext(ctx); - var client = NetmapServiceClient ?? (callInvoker != null - ? new NetmapService.NetmapServiceClient(callInvoker) - : new NetmapService.NetmapServiceClient(ClientCtx.Channel)); + if (NetmapServiceProvider == null) + { + var invoker = CreateInvoker(); - return new NetmapServiceProvider(client, ClientCtx); + NetmapServiceClient = NetmapServiceClient ?? ( + invoker != null + ? new NetmapServiceClient(invoker) + : new NetmapServiceClient(ClientCtx.Channel)); + + NetmapServiceProvider = new NetmapServiceProvider(NetmapServiceClient, ClientCtx); + } + + return NetmapServiceProvider; } - private SessionServiceProvider GetSessionService(IContext ctx) + private SessionServiceProvider GetSessionService() { - var callInvoker = SetupClientContext(ctx); - var client = SessionServiceClient ?? (callInvoker != null - ? new SessionService.SessionServiceClient(callInvoker) - : new SessionService.SessionServiceClient(ClientCtx.Channel)); + if (SessionServiceProvider == null) + { + var invoker = CreateInvoker(); + + SessionServiceClient = SessionServiceClient ?? ( + invoker != null + ? new SessionServiceClient(invoker) + : new SessionServiceClient(ClientCtx.Channel)); + + SessionServiceProvider = new SessionServiceProvider(SessionServiceClient, ClientCtx); + } + + return SessionServiceProvider; - return new SessionServiceProvider(client, ClientCtx); } - private ApeManagerServiceProvider GetApeManagerService(IContext ctx) + private ApeManagerServiceProvider GetApeManagerService() { - var callInvoker = SetupClientContext(ctx); - var client = ApeManagerServiceClient ?? (callInvoker != null - ? new APEManagerService.APEManagerServiceClient(callInvoker) - : new APEManagerService.APEManagerServiceClient(ClientCtx.Channel)); + if (ApeManagerServiceProvider == null) + { + var invoker = CreateInvoker(); - return new ApeManagerServiceProvider(client, ClientCtx); + ApeManagerServiceClient = ApeManagerServiceClient ?? ( + invoker != null + ? new APEManagerServiceClient(invoker) + : new APEManagerServiceClient(ClientCtx.Channel)); + + ApeManagerServiceProvider = new ApeManagerServiceProvider(ApeManagerServiceClient, ClientCtx); + } + + return ApeManagerServiceProvider; } - private AccountingServiceProvider GetAccouningService(IContext ctx) + private AccountingServiceProvider GetAccouningService() { - var callInvoker = SetupClientContext(ctx); - var client = AccountingServiceClient ?? (callInvoker != null - ? new AccountingService.AccountingServiceClient(callInvoker) - : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); + if (this.AccountingServiceProvider == null) + { + var invoker = CreateInvoker(); - return new AccountingServiceProvider(client, ClientCtx); + AccountingServiceClient = AccountingServiceClient ?? ( + invoker != null + ? new AccountingServiceClient(invoker) + : new AccountingServiceClient(ClientCtx.Channel)); + + AccountingServiceProvider = new AccountingServiceProvider(AccountingServiceClient, ClientCtx); + } + + return AccountingServiceProvider; } - private ContainerServiceProvider GetContainerService(IContext ctx) + private ContainerServiceProvider GetContainerService() { - var callInvoker = SetupClientContext(ctx); - var client = ContainerServiceClient ?? (callInvoker != null - ? new ContainerService.ContainerServiceClient(callInvoker) - : new ContainerService.ContainerServiceClient(ClientCtx.Channel)); + if (this.ContainerServiceProvider == null) + { + var invoker = CreateInvoker(); - return new ContainerServiceProvider(client, ClientCtx); + ContainerServiceClient = ContainerServiceClient ?? ( + invoker != null + ? new ContainerServiceClient(invoker) + : new ContainerServiceClient(ClientCtx.Channel)); + + ContainerServiceProvider = new ContainerServiceProvider(ContainerServiceClient, ClientCtx); + } + + return ContainerServiceProvider; } - private ObjectServiceProvider GetObjectService(IContext ctx) + private ObjectServiceProvider GetObjectService() { - var callInvoker = SetupClientContext(ctx); - var client = ObjectServiceClient ?? (callInvoker != null - ? new ObjectService.ObjectServiceClient(callInvoker) - : new ObjectService.ObjectServiceClient(ClientCtx.Channel)); + if (this.ObjectServiceProvider == null) + { + var invoker = CreateInvoker(); - return new ObjectServiceProvider(client, ClientCtx); + ObjectServiceClient = ObjectServiceClient ?? ( + invoker != null + ? new ObjectServiceClient(invoker) + : new ObjectServiceClient(ClientCtx.Channel)); + + ObjectServiceProvider = new ObjectServiceProvider(ObjectServiceClient, ClientCtx); + } + + return ObjectServiceProvider; } - private AccountingServiceProvider GetAccountService(IContext ctx) - { - var callInvoker = SetupClientContext(ctx); - var client = AccountingServiceClient ?? (callInvoker != null - ? new AccountingService.AccountingServiceClient(callInvoker) - : new AccountingService.AccountingServiceClient(ClientCtx.Channel)); - - return new AccountingServiceProvider(client, ClientCtx); - } private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) { @@ -559,7 +561,7 @@ public class FrostFSClient : IFrostFSClient { var prm = new PrmBalance(ctx); - var service = GetAccouningService(prm); + var service = GetAccouningService(); _ = await service.GetBallance(prm).ConfigureAwait(false); return null; diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 456962f..8520528 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -21,7 +21,7 @@ public interface IFrostFSClient : IDisposable #endregion #region ApeManager - Task AddChainAsync(PrmApeChainAdd args); + Task> AddChainAsync(PrmApeChainAdd args); Task RemoveChainAsync(PrmApeChainRemove args); @@ -63,7 +63,7 @@ public interface IFrostFSClient : IDisposable #endregion #region Tools - FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx); + FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header); #endregion public Task Dial(CallContext ctx); diff --git a/src/FrostFS.SDK.Client/Mappers/ContainerId.cs b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs index fe8632f..df27320 100644 --- a/src/FrostFS.SDK.Client/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs @@ -43,6 +43,6 @@ public static class ContainerIdMapper throw new ArgumentNullException(nameof(message)); } - return new FrostFsContainerId(Base58.Encode(message.Value.ToByteArray())); + return new FrostFsContainerId(Base58.Encode(message.Value.Span)); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs index 747468b..d7340eb 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/NodeInfo.cs @@ -39,7 +39,7 @@ public static class NodeInfoMapper state: state, addresses: [.. nodeInfo.Addresses], attributes: nodeInfo.Attributes.ToDictionary(n => n.Key, n => n.Value), - publicKey: nodeInfo.PublicKey.ToByteArray() + publicKey: nodeInfo.PublicKey.Memory ); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/Object/Object.cs b/src/FrostFS.SDK.Client/Mappers/Object/Object.cs index b9d1520..5f85fc5 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/Object.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/Object.cs @@ -6,7 +6,7 @@ internal static class ObjectMapper { return new FrostFsObject(obj.Header.ToModel()) { - ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.ToByteArray()) + ObjectId = FrostFsObjectId.FromHash(obj.ObjectId.Value.Span) }; } } diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs index 1ee29a6..317b31c 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs @@ -40,7 +40,7 @@ public static class ObjectHeaderMapper } var model = new FrostFsObjectHeader( - new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.ToByteArray())), + new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)), objTypeName, header.Attributes.Select(attribute => attribute.ToModel()).ToArray(), split, diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs index 373edfb..343e3de 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs @@ -28,6 +28,6 @@ public static class ObjectIdMapper throw new ArgumentNullException(nameof(objectId)); } - return FrostFsObjectId.FromHash(objectId.Value.ToByteArray()); + return FrostFsObjectId.FromHash(objectId.Value.Span); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/OwnerId.cs b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs index 297178a..6739a0b 100644 --- a/src/FrostFS.SDK.Client/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs @@ -44,7 +44,7 @@ public static class OwnerIdMapper if (!Caches.Owners.TryGetValue(message, out FrostFsOwner? model)) { - model = new FrostFsOwner(Base58.Encode(message.Value.ToByteArray())); + model = new FrostFsOwner(Base58.Encode(message.Value.Span)); Caches.Owners.Set(message, model, _oneHourExpiration); } diff --git a/src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs b/src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs index 6ebec1f..4aa00f9 100644 --- a/src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs +++ b/src/FrostFS.SDK.Client/Models/Client/ClientSettings.cs @@ -3,6 +3,8 @@ using System.Collections.ObjectModel; using System.Globalization; using System.Text; +using Grpc.Core.Interceptors; + namespace FrostFS.SDK; public class ClientSettings @@ -11,60 +13,25 @@ public class ClientSettings public string Host { get; set; } = string.Empty; - public virtual void Validate() - { - var errors = CheckFields(); - if (errors != null) - ThrowSettingsException(errors); - } - - protected Collection? CheckFields() - { - if (string.IsNullOrWhiteSpace(Host)) - { - var error = string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host)); - return new Collection([error]); - } - - return null; - } - - protected static void ThrowSettingsException(Collection errors) - { - if (errors is null) - { - throw new ArgumentNullException(nameof(errors)); - } - - StringBuilder messages = new(); - - foreach (var error in errors) - { - messages.AppendLine(error); - } - - throw new ArgumentException(messages.ToString()); - } -} - -public class SingleOwnerClientSettings : ClientSettings -{ public string Key { get; set; } = string.Empty; - public override void Validate() - { - var errors = CheckFields(); - if (errors != null) - ThrowSettingsException(errors); - } + public Action? Callback { get; set; } - protected new Collection? CheckFields() + public Collection Interceptors { get; } = []; + + public void Validate() { - Collection? errors = base.CheckFields(); + StringBuilder? errors = null; + + if (string.IsNullOrWhiteSpace(Host)) + (errors = new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Host))); if (string.IsNullOrWhiteSpace(Key)) - (errors ??= []).Add(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key))); + (errors ??= new StringBuilder(128)).AppendLine(string.Format(CultureInfo.InvariantCulture, errorTemplate, nameof(Key))); - return errors; + if (errors != null) + { + throw new ArgumentException(errors.ToString()); + } } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs index f0b3fdf..9f081cd 100644 --- a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs @@ -27,7 +27,7 @@ public class FrostFsContainerId if (containerID != null) { - this.modelId = Base58.Encode(containerID.Value.ToByteArray()); + this.modelId = Base58.Encode(containerID.Value.Span); return this.modelId; } diff --git a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs index 6223a7f..af4ffdc 100644 --- a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs @@ -96,7 +96,7 @@ public class FrostFsContainerInfo PlacementPolicy = PlacementPolicy.Value.GetPolicy(), Nonce = ByteString.CopyFrom(Nonce.ToBytes()), OwnerId = Owner?.OwnerID, - Version = Version?.Version + Version = Version?.VersionID }; var attribs = GetGrpsAttributes(); diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs index 6fd0afc..81e642d 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs @@ -10,7 +10,7 @@ public class FrostFsVersion(int major, int minor) public int Major { get; set; } = major; public int Minor { get; set; } = minor; - internal Version Version + internal Version VersionID { get { diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs index 49fde5a..57300a3 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsObjectId.cs @@ -8,13 +8,8 @@ public class FrostFsObjectId(string id) { public string Value { get; } = id; - public static FrostFsObjectId FromHash(byte[] hash) + public static FrostFsObjectId FromHash(ReadOnlySpan hash) { - if (hash is null) - { - throw new ArgumentNullException(nameof(hash)); - } - if (hash.Length != Constants.Sha256HashLength) throw new FormatException("ObjectID must be a sha256 hash."); diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs index 1bd6754..228edf2 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsOwner.cs @@ -4,6 +4,7 @@ using FrostFS.Refs; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; + namespace FrostFS.SDK; public class FrostFsOwner(string id) diff --git a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs index 672754d..54f0615 100644 --- a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs +++ b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs @@ -1,9 +1,117 @@ using System; +using FrostFS.Refs; + +using FrostFS.SDK.Client; +using FrostFS.SDK.Cryptography; +using FrostFS.Session; + +using Google.Protobuf; + namespace FrostFS.SDK; -public class FrostFsSessionToken(byte[] token, Guid id) +public class FrostFsSessionToken { - public Guid Id { get; private set; } = id; - public byte[] Token { get; private set; } = token; + private Guid _id; + private ReadOnlyMemory _sessionKey; + private readonly SessionToken.Types.Body _body; + + private FrostFsSessionToken() + { + ProtoId = ByteString.Empty; + ProtoSessionKey = ByteString.Empty; + _body = new SessionToken.Types.Body(); + } + + internal FrostFsSessionToken(SessionToken token) + { + ProtoId = token.Body.Id; + ProtoSessionKey = token.Body.SessionKey; + + _body = token.Body; + } + + public Guid Id + { + get + { + if (_id == Guid.Empty) + _id = ProtoId.ToUuid(); + + return _id; + } + } + + public ReadOnlyMemory SessionKey + { + get + { + if (_sessionKey.IsEmpty) + _sessionKey = ProtoSessionKey.Memory; + + return _sessionKey; + } + } + + internal ByteString ProtoId { get; } + + internal ByteString ProtoSessionKey { get; } + + public SessionToken CreateContainerToken(ContainerID? containerId, ContainerSessionContext.Types.Verb verb, ClientKey key) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + SessionToken sessionToken = new() { Body = _body.Clone() }; + + sessionToken.Body.Container = new() { Verb = verb }; + + if (containerId != null) + sessionToken.Body.Container.ContainerId = containerId; + else + sessionToken.Body.Container.Wildcard = true; + + sessionToken.Body.SessionKey = key.PublicKeyProto; + + sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); + + return sessionToken; + } + + public SessionToken CreateObjectTokenContext(Address address, ObjectSessionContext.Types.Verb verb, ClientKey key) + { + if (address is null) + { + throw new ArgumentNullException(nameof(address)); + } + + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + SessionToken sessionToken = new() + { + Body = _body.Clone() + }; + + ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId }; + + if (address.ObjectId != null) + target.Objects.Add(address.ObjectId); + + sessionToken.Body.Object = new() + { + Target = target, + Verb = verb + }; + + sessionToken.Body.SessionKey = key.PublicKeyProto; + + sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); + + return sessionToken; + } } diff --git a/src/FrostFS.SDK.Client/Parameters/CallContext.cs b/src/FrostFS.SDK.Client/Parameters/CallContext.cs index 323a990..05e061c 100644 --- a/src/FrostFS.SDK.Client/Parameters/CallContext.cs +++ b/src/FrostFS.SDK.Client/Parameters/CallContext.cs @@ -1,27 +1,16 @@ using System; -using System.Collections.ObjectModel; -using System.Security.Cryptography; using System.Threading; -using FrostFS.SDK.Cryptography; - -using Google.Protobuf; - -using Grpc.Core.Interceptors; - namespace FrostFS.SDK.Client; public class CallContext() { - private ByteString? publicKeyCache; - internal Action? PoolErrorHandler { get; set; } + // internal Action? PoolErrorHandler { get; set; } - public ECDsa? Key { get; set; } + // public FrostFsOwner? OwnerId { get; set; } - public FrostFsOwner? OwnerId { get; set; } - - public FrostFsVersion? Version { get; set; } + // public FrostFsVersion? Version { get; set; } public CancellationToken CancellationToken { get; set; } @@ -29,17 +18,7 @@ public class CallContext() public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; - public Action? Callback { get; set; } + // public Action? Callback { get; set; } - public Collection Interceptors { get; } = []; - - public ByteString? GetPublicKeyCache() - { - if (publicKeyCache == null && Key != null) - { - publicKeyCache = ByteString.CopyFrom(Key.PublicKey()); - } - - return publicKeyCache; - } + // public Collection Interceptors { get; } = []; } diff --git a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs index 7e50337..de984d6 100644 --- a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs +++ b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs @@ -9,10 +9,8 @@ namespace FrostFS.SDK.Client; public class ClientWrapper : ClientStatusMonitor { private readonly object _lock = new(); - private SessionCache sessionCache; - internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address) { WrapperPrm = wrapperPrm; @@ -140,6 +138,7 @@ public class ClientWrapper : ClientStatusMonitor catch (FrostFsException) { SetUnhealthy(); + return wasHealthy; } diff --git a/src/FrostFS.SDK.Client/Pool/InitParameters.cs b/src/FrostFS.SDK.Client/Pool/InitParameters.cs index 480820e..3f4d14e 100644 --- a/src/FrostFS.SDK.Client/Pool/InitParameters.cs +++ b/src/FrostFS.SDK.Client/Pool/InitParameters.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.ObjectModel; using System.Security.Cryptography; +using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Logging; @@ -33,4 +35,8 @@ public class InitParameters public ulong GracefulCloseOnSwitchTimeout { get; set; } public ILogger? Logger { get; set; } + + public Action? Callback { get; set; } + + public Collection Interceptors { get; } = []; } diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index 105978d..acb60b9 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -35,18 +35,17 @@ public partial class Pool : IFrostFSClient private InnerPool[]? InnerPools { get; set; } - private ECDsa Key { get; set; } - - private string PublicKey { get; } + private ClientKey Key { get; set; } private OwnerID? _ownerId; + private FrostFsOwner? _owner; private FrostFsOwner Owner { get { - _owner ??= new FrostFsOwner(Key.PublicKey().PublicKeyToAddress()); + _owner ??= new FrostFsOwner(Key.ECDsaKey.PublicKey().PublicKeyToAddress()); return _owner; } } @@ -57,7 +56,7 @@ public partial class Pool : IFrostFSClient { if (_ownerId == null) { - _owner = new FrostFsOwner(Key.PublicKey().PublicKeyToAddress()); + _owner = Key.Owner; _ownerId = _owner.ToMessage(); } return _ownerId; @@ -101,8 +100,7 @@ public partial class Pool : IFrostFSClient FillDefaultInitParams(options, this); - Key = options.Key; - PublicKey = $"{Key.PublicKey()}"; + Key = new ClientKey(options.Key); SessionCache = cache; logger = options.Logger; @@ -115,16 +113,9 @@ public partial class Pool : IFrostFSClient options.SessionExpirationDuration); ClientBuilder = options.ClientBuilder!; - } - private void SetupContext(CallContext ctx) - { - if (ctx == null) - { - throw new ArgumentNullException(nameof(ctx)); - } + // ClientContext.PoolErrorHandler = client.HandleError; - ctx.Key ??= Key; } // Dial establishes a connection to the servers from the FrostFS network. @@ -137,8 +128,6 @@ public partial class Pool : IFrostFSClient // See also InitParameters.SetClientRebalanceInterval. public async Task Dial(CallContext ctx) { - SetupContext(ctx); - var inner = new InnerPool[RebalanceParams.NodesParams.Length]; bool atLeastOneHealthy = false; @@ -158,11 +147,11 @@ public partial class Pool : IFrostFSClient await client.Dial(ctx).ConfigureAwait(false); dialed = true; - var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key, false) + var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key.ECDsaKey, false) .ConfigureAwait(false); - var key = FormCacheKey(nodeParams.Addresses[j], Key.PrivateKey().ToString()); - _ = SessionCache.Cache[key] = token; + var key = FormCacheKey(nodeParams.Addresses[j], Key.PublicKey); + SessionCache.SetValue(key, token); atLeastOneHealthy = true; } @@ -291,12 +280,14 @@ public partial class Pool : IFrostFSClient var wrapperPrm = new WrapperPrm { Address = address, - Key = parameters.Key, + Key = parameters.Key!, Logger = parameters.Logger, DialTimeout = parameters.NodeDialTimeout, StreamTimeout = parameters.NodeStreamTimeout, ErrorThreshold = parameters.ErrorThreshold, - GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout + GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout, + Callback = parameters.Callback, + Interceptors = parameters.Interceptors }; return new ClientWrapper(wrapperPrm, pool); @@ -318,7 +309,7 @@ public partial class Pool : IFrostFSClient throw new FrostFsException("Cannot find alive client"); } - private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) + private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) { var client = cw.Client; var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false); @@ -521,7 +512,6 @@ public partial class Pool : IFrostFSClient var client = Connection(); args ??= new(); - args.Context.PoolErrorHandler = client.HandleError; return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false); } @@ -529,278 +519,126 @@ public partial class Pool : IFrostFSClient public async Task GetNodeInfoAsync(PrmNodeInfo? args = null) { var client = Connection(); - - args ??= new(); - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false); } public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null) { var client = Connection(); - - args ??= new(); - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false); } public async Task CreateSessionAsync(PrmSessionCreate args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.CreateSessionAsync(args).ConfigureAwait(false); } - public async Task AddChainAsync(PrmApeChainAdd args) + public async Task> AddChainAsync(PrmApeChainAdd args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.AddChainAsync(args).ConfigureAwait(false); } public async Task RemoveChainAsync(PrmApeChainRemove args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - await client.Client!.RemoveChainAsync(args).ConfigureAwait(false); } public async Task ListChainAsync(PrmApeChainList args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.ListChainAsync(args).ConfigureAwait(false); } public async Task GetContainerAsync(PrmContainerGet args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetContainerAsync(args).ConfigureAwait(false); } public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) { var client = Connection(); - - args ??= new(); - args.Context.PoolErrorHandler = client.HandleError; - return client.Client!.ListContainersAsync(args); } public async Task CreateContainerAsync(PrmContainerCreate args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.CreateContainerAsync(args).ConfigureAwait(false); } public async Task DeleteContainerAsync(PrmContainerDelete args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - await client.Client!.DeleteContainerAsync(args).ConfigureAwait(false); } public async Task GetObjectHeadAsync(PrmObjectHeadGet args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetObjectHeadAsync(args).ConfigureAwait(false); } public async Task GetObjectAsync(PrmObjectGet args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetObjectAsync(args).ConfigureAwait(false); } public async Task PutObjectAsync(PrmObjectPut args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.PutObjectAsync(args).ConfigureAwait(false); } public async Task PutSingleObjectAsync(PrmSingleObjectPut args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false); } public async Task PatchObjectAsync(PrmObjectPatch args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); } public async Task GetRangeAsync(PrmRangeGet args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetRangeAsync(args).ConfigureAwait(false); } public async Task[]> GetRangeHashAsync(PrmRangeHashGet args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetRangeHashAsync(args).ConfigureAwait(false); } public async Task PatchAsync(PrmObjectPatch args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); } public async Task DeleteObjectAsync(PrmObjectDelete args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - await client.Client!.DeleteObjectAsync(args).ConfigureAwait(false); } public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - var client = Connection(); - - args.Context.PoolErrorHandler = client.HandleError; - return client.Client!.SearchObjectsAsync(args); } public async Task GetBalanceAsync(PrmBalance? args) { var client = Connection(); - - args ??= new(); - args.Context.PoolErrorHandler = client.HandleError; - return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false); } @@ -824,7 +662,7 @@ public partial class Pool : IFrostFSClient GC.SuppressFinalize(this); } - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) { throw new NotImplementedException(); } diff --git a/src/FrostFS.SDK.Client/Pool/SessionCache.cs b/src/FrostFS.SDK.Client/Pool/SessionCache.cs index 829b1c9..fae3de1 100644 --- a/src/FrostFS.SDK.Client/Pool/SessionCache.cs +++ b/src/FrostFS.SDK.Client/Pool/SessionCache.cs @@ -1,23 +1,49 @@ using System; -using System.Collections; +using System.Collections.Concurrent; namespace FrostFS.SDK.Client; -internal struct SessionCache(ulong sessionExpirationDuration) +internal sealed class SessionCache(ulong sessionExpirationDuration) { - internal Hashtable Cache { get; } = []; + private ConcurrentDictionary _cache { get; } = []; internal ulong CurrentEpoch { get; set; } internal ulong TokenDuration { get; set; } = sessionExpirationDuration; + internal bool Contains(string key) + { + return _cache.ContainsKey(key); + } + + internal bool TryGetValue(string? key, out FrostFsSessionToken? value) + { + if (key == null) + { + value = null; + return false; + } + + var ok = _cache.TryGetValue(key, out value); + + return ok && value != null; + } + + internal void SetValue(string? key, FrostFsSessionToken value) + { + if (key != null) + { + _cache[key] = value; + } + } + internal void DeleteByPrefix(string prefix) { - foreach (var key in Cache.Keys) + foreach (var key in _cache.Keys) { - if (((string)key).StartsWith(prefix, StringComparison.Ordinal)) + if (key.StartsWith(prefix, StringComparison.Ordinal)) { - Cache.Remove(key); + _cache.TryRemove(key, out var _); } } } diff --git a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs index 3c23505..b68b6ee 100644 --- a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs +++ b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs @@ -1,13 +1,14 @@ using System; +using System.Collections.ObjectModel; using System.Security.Cryptography; +using Grpc.Core.Interceptors; using Grpc.Net.Client; using Microsoft.Extensions.Logging; namespace FrostFS.SDK.Client; -// wrapperPrm is params to create clientWrapper. [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] public struct WrapperPrm { @@ -15,7 +16,7 @@ public struct WrapperPrm internal string Address { get; set; } - internal ECDsa? Key { get; set; } + internal ECDsa Key { get; set; } internal ulong DialTimeout { get; set; } @@ -30,5 +31,9 @@ public struct WrapperPrm internal GrpcChannelOptions GrpcChannelOptions { get; set; } internal ulong GracefulCloseOnSwitchTimeout { get; set; } + + internal Action? Callback { get; set; } + + internal Collection? Interceptors { get; set; } } diff --git a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs index ba8ba2a..bdb8e61 100644 --- a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs @@ -24,12 +24,12 @@ internal sealed class AccountingServiceProvider : ContextAccessor { Body = new() { - OwnerId = ctx.OwnerId!.OwnerID + OwnerId = ClientContext.Owner.OwnerID } }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key!); + request.Sign(ClientContext.Key.ECDsaKey); var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 840223d..1c22cdc 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -16,13 +16,9 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor _apeManagerServiceClient = apeManagerServiceClient; } - internal async Task AddChainAsync(PrmApeChainAdd args) + internal async Task> AddChainAsync(PrmApeChainAdd args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); AddChainRequest request = new() { @@ -34,22 +30,18 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await _apeManagerServiceClient!.AddChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); Verifier.CheckResponse(response); - return response.Body.ChainId.ToByteArray(); + return response.Body.ChainId.Memory; } internal async Task RemoveChainAsync(PrmApeChainRemove args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); RemoveChainRequest request = new() { @@ -61,7 +53,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await _apeManagerServiceClient!.RemoveChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -71,10 +63,6 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor internal async Task ListChainAsync(PrmApeChainList args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); ListChainsRequest request = new() { @@ -85,7 +73,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await _apeManagerServiceClient!.ListChainsAsync(request, null, ctx.Deadline, ctx.CancellationToken); diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index 3a0379e..fcdc0e5 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Security.Cryptography; using System.Threading.Tasks; using FrostFS.Container; @@ -12,26 +13,34 @@ using FrostFS.Session; namespace FrostFS.SDK.Client; -internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx), ISessionProvider +internal sealed class ContainerServiceProvider(ContainerService.ContainerServiceClient service, ClientContext clientCtx) : ContextAccessor(clientCtx) { private SessionProvider? sessions; - public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) + public async ValueTask GetDefaultSession(ISessionToken args, CallContext ctx) { sessions ??= new(ClientContext); - if (ClientContext.SessionCache.Cache != null && - ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey)) + if (!ClientContext.SessionCache!.TryGetValue(ClientContext.SessionCacheKey, out var token)) { - return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey]; + var protoToken = await sessions.GetDefaultSession(args, ctx).ConfigureAwait(false); + + token = new FrostFsSessionToken(protoToken); + + ClientContext.SessionCache.SetValue(ClientContext.SessionCacheKey, token); } - return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); + if (token == null) + { + throw new FrostFsException("Cannot create session"); + } + + return token; } internal async Task GetContainerAsync(PrmContainerGet args) { - GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, args.Context); + GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, ClientContext.Key.ECDsaKey); var response = await service.GetAsync(request, null, args.Context.Deadline, args.Context.CancellationToken); @@ -43,24 +52,17 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) { var ctx = args.Context!; - ctx.OwnerId ??= ClientContext.Owner; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - if (ctx.OwnerId == null) - throw new ArgumentException(nameof(ctx.OwnerId)); var request = new ListRequest { Body = new() { - OwnerId = ctx.OwnerId.ToMessage() + OwnerId = ClientContext.Owner.OwnerID } }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -68,7 +70,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService foreach (var cid in response.Body.ContainerIds) { - yield return new FrostFsContainerId(Base58.Encode(cid.Value.ToByteArray())); + yield return new FrostFsContainerId(Base58.Encode(cid.Value.Span)); } } @@ -78,36 +80,28 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService var grpcContainer = args.Container.GetContainer(); - grpcContainer.OwnerId ??= ctx.OwnerId?.ToMessage(); - grpcContainer.Version ??= ctx.Version?.ToMessage(); - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - if (grpcContainer.OwnerId == null) - throw new ArgumentException(nameof(grpcContainer.OwnerId)); - if (grpcContainer.Version == null) - throw new ArgumentException(nameof(grpcContainer.Version)); + grpcContainer.OwnerId ??= ClientContext.Owner.OwnerID; + grpcContainer.Version ??= ClientContext.Version.VersionID; var request = new PutRequest { Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = ctx.Key.SignRFC6979(grpcContainer) + Signature = ClientContext.Key.ECDsaKey.SignRFC6979(grpcContainer) } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = (args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false)) ?? throw new FrostFsException("Cannot create session token"); - sessionToken.CreateContainerTokenContext( + var protoToken = sessionToken.CreateContainerToken( null, ContainerSessionContext.Types.Verb.Put, - ctx.Key, - ctx.GetPublicKeyCache()!); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -121,29 +115,26 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async Task DeleteContainerAsync(PrmContainerDelete args) { var ctx = args.Context!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var request = new DeleteRequest { Body = new DeleteRequest.Types.Body { ContainerId = args.ContainerId.ToMessage(), - Signature = ctx.Key.SignRFC6979(args.ContainerId.ToMessage().Value) + Signature = ClientContext.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value) } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateContainerTokenContext( + var protoToken = sessionToken.CreateContainerToken( request.Body.ContainerId, ContainerSessionContext.Types.Verb.Delete, - ctx.Key, - ctx.GetPublicKeyCache()!); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -155,11 +146,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Verifier.CheckResponse(response); } - private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, CallContext ctx) + private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, ECDsa key) { - if (ctx.Key == null) - throw new ArgumentNullException(nameof(ctx), "Key is null"); - var request = new GetRequest { Body = new GetRequest.Types.Body @@ -169,7 +157,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService }; request.AddMetaHeader(xHeaders); - request.Sign(ctx.Key); + request.Sign(key); return request; } @@ -182,7 +170,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx) { - var request = GetContainerRequest(id, null, ctx); + var request = GetContainerRequest(id, null, ClientContext.Key.ECDsaKey); async Task action() { diff --git a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs index 18649d8..570bd24 100644 --- a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs @@ -1,6 +1,4 @@ using System; -using System.Linq; -using System.Text; using System.Threading.Tasks; using FrostFS.Netmap; @@ -47,10 +45,6 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var request = new LocalNodeInfoRequest { @@ -58,7 +52,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -69,15 +63,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetworkInfoAsync(CallContext ctx) { - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(ctx), "Key is null"); - var request = new NetworkInfoRequest(); request.AddMetaHeader(null); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken) .ConfigureAwait(false); @@ -90,15 +79,11 @@ internal sealed class NetmapServiceProvider : ContextAccessor internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var request = new NetmapSnapshotRequest(); request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -107,12 +92,16 @@ internal sealed class NetmapServiceProvider : ContextAccessor return response.ToModel(); } - private static bool GetBoolValue(byte[] bytes) + private static bool GetBoolValue(ReadOnlySpan bytes) { - return bytes.Any(b => b != 0); + for (int i = bytes.Length - 1; i >= 0; i--) + if (bytes[i] != 0) + return true; + + return false; } - private static ulong GetLongValue(byte[] bytes) + private static ulong GetLongValue(ReadOnlySpan bytes) { ulong val = 0; for (var i = bytes.Length - 1; i >= 0; i--) @@ -123,24 +112,50 @@ internal sealed class NetmapServiceProvider : ContextAccessor private static void SetNetworksParam(Parameter param, NetworkSettings settings) { - var key = Encoding.UTF8.GetString(param.Key.ToByteArray()); + var key = param.Key.ToStringUtf8(); - var valueBytes = param.Value.ToByteArray(); + var valueBytes = param.Value.Span; switch (key) { - case "AuditFee": settings.AuditFee = GetLongValue(valueBytes); break; - case "BasicIncomeRate": settings.BasicIncomeRate = GetLongValue(valueBytes); break; - case "ContainerFee": settings.ContainerFee = GetLongValue(valueBytes); break; - case "ContainerAliasFee": settings.ContainerAliasFee = GetLongValue(valueBytes); break; - case "EpochDuration": settings.EpochDuration = GetLongValue(valueBytes); break; - case "InnerRingCandidateFee": settings.InnerRingCandidateFee = GetLongValue(valueBytes); break; - case "MaxECDataCount": settings.MaxECDataCount = GetLongValue(valueBytes); break; - case "MaxECParityCount": settings.MaxECParityCount = GetLongValue(valueBytes); break; - case "MaxObjectSize": settings.MaxObjectSize = GetLongValue(valueBytes); break; - case "WithdrawFee": settings.WithdrawFee = GetLongValue(valueBytes); break; - case "HomomorphicHashingDisabled": settings.HomomorphicHashingDisabled = GetBoolValue(valueBytes); break; - case "MaintenanceModeAllowed": settings.MaintenanceModeAllowed = GetBoolValue(valueBytes); break; - default: settings.UnnamedSettings.Add(key, valueBytes); break; + case "AuditFee": + settings.AuditFee = GetLongValue(valueBytes); + break; + case "BasicIncomeRate": + settings.BasicIncomeRate = GetLongValue(valueBytes); + break; + case "ContainerFee": + settings.ContainerFee = GetLongValue(valueBytes); + break; + case "ContainerAliasFee": + settings.ContainerAliasFee = GetLongValue(valueBytes); + break; + case "EpochDuration": + settings.EpochDuration = GetLongValue(valueBytes); + break; + case "InnerRingCandidateFee": + settings.InnerRingCandidateFee = GetLongValue(valueBytes); + break; + case "MaxECDataCount": + settings.MaxECDataCount = GetLongValue(valueBytes); + break; + case "MaxECParityCount": + settings.MaxECParityCount = GetLongValue(valueBytes); + break; + case "MaxObjectSize": + settings.MaxObjectSize = GetLongValue(valueBytes); + break; + case "WithdrawFee": + settings.WithdrawFee = GetLongValue(valueBytes); + break; + case "HomomorphicHashingDisabled": + settings.HomomorphicHashingDisabled = GetBoolValue(valueBytes); + break; + case "MaintenanceModeAllowed": + settings.MaintenanceModeAllowed = GetBoolValue(valueBytes); + break; + default: + settings.UnnamedSettings.Add(key, valueBytes.ToArray()); + break; } } } diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 7737c62..d3c4347 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -17,31 +17,35 @@ using Google.Protobuf; namespace FrostFS.SDK.Client; internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient client, ClientContext clientCtx) - : ContextAccessor(clientCtx), ISessionProvider + : ContextAccessor(clientCtx) { private SessionProvider? sessions; private readonly ObjectService.ObjectServiceClient client = client; - public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) + public async ValueTask GetDefaultSession(ISessionToken args, CallContext ctx) { sessions ??= new(ClientContext); - if (ClientContext.SessionCache.Cache != null && - ClientContext.SessionCache.Cache.ContainsKey(ClientContext.SessionCacheKey)) + if (!ClientContext.SessionCache!.TryGetValue(ClientContext.SessionCacheKey, out var token)) { - return (SessionToken)ClientContext.SessionCache.Cache[ClientContext.SessionCacheKey]; + var protoToken = await sessions.GetDefaultSession(args, ctx).ConfigureAwait(false); + + token = new FrostFsSessionToken(protoToken); + + ClientContext.SessionCache.SetValue(ClientContext.SessionCacheKey, token); } - return await sessions.GetOrCreateSession(args, ctx).ConfigureAwait(false); + if (token == null) + { + throw new FrostFsException("Cannot create session"); + } + + return token; } internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var request = new HeadRequest { @@ -55,16 +59,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Head, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); @@ -77,11 +81,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - var request = new GetRequest { Body = new GetRequest.Types.Body @@ -94,16 +93,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Get, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); return await GetObject(request, ctx).ConfigureAwait(false); } @@ -112,11 +111,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - var request = new GetRangeRequest { Body = new GetRangeRequest.Types.Body @@ -135,16 +129,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Range, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var call = client.GetRange(request, null, ctx.Deadline, ctx.CancellationToken); return new RangeReader(call); @@ -154,11 +148,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - var request = new GetRangeHashRequest { Body = new GetRangeHashRequest.Types.Body @@ -182,16 +171,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl }); } - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Rangehash, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await client.GetRangeHashAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -206,10 +195,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl internal async Task DeleteObjectAsync(PrmObjectDelete args) { var ctx = args.Context!; - ctx.Key ??= ClientContext.Key?.ECDsaKey; - - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var request = new DeleteRequest { @@ -223,15 +208,15 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( request.Body.Address, ObjectSessionContext.Types.Verb.Delete, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); - request.Sign(ctx.Key); + request.AddMetaHeader(args.XHeaders, protoToken); + request.Sign(ClientContext.Key.ECDsaKey); var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); @@ -242,9 +227,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - var request = new SearchRequest { Body = new SearchRequest.Types.Body @@ -256,22 +238,30 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.Body.Filters.AddRange(args.Filters.Select(f => f.ToMessage())); - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( new Address { ContainerId = request.Body.ContainerId }, ObjectSessionContext.Types.Verb.Search, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); - var objectsIds = SearchObjects(request, ctx); + using var stream = GetSearchReader(request, ctx); - await foreach (var oid in objectsIds) + while (true) { - yield return FrostFsObjectId.FromHash(oid.Value.ToByteArray()); + var ids = await stream.Read(ctx.CancellationToken).ConfigureAwait(false); + + if (ids == null) + yield break; + + foreach (var oid in ids) + { + yield return FrostFsObjectId.FromHash(oid.Value.Span); + } } } @@ -296,6 +286,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl args.FullLength = args.Header.PayloadLength; else if (args.Payload.CanSeek) args.FullLength = (ulong)args.Payload.Length; + else + throw new ArgumentException("The stream does not have a length and payload length is not defined"); var response = await PutStreamObject(args).ConfigureAwait(false); @@ -307,39 +299,34 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - - var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ctx); + var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ClientContext); var request = new PutSingleRequest { Body = new() { Object = grpcObject } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId }, ObjectSessionContext.Types.Verb.Put, - ctx.Key); + ClientContext.Key); - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); Verifier.CheckResponse(response); - return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.ToByteArray()); + return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.Span); } internal async Task PatchObjectAsync(PrmObjectPatch args) { var ctx = args.Context!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var chunkSize = args.MaxPayloadPatchChunkLength; Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); @@ -350,7 +337,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl try { // common - chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + chunkBuffer = ArrayPool.Shared.Rent(chunkSize); var address = new Address { @@ -358,13 +345,12 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl ContainerId = args.Address.ContainerId }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( address, ObjectSessionContext.Types.Verb.Patch, - ctx.Key - ); + ClientContext.Key); var request = new PatchRequest() { @@ -403,9 +389,9 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl currentPos += (ulong)bytesCount; - request.AddMetaHeader(args.XHeaders, sessionToken); + request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ctx.Key); + request.Sign(ClientContext.Key.ECDsaKey); await call.RequestStream.WriteAsync(request).ConfigureAwait(false); @@ -433,20 +419,17 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var ctx = args.Context!; - var tokenRaw = await GetOrCreateSession(args, ctx).ConfigureAwait(false); - var token = new FrostFsSessionToken(tokenRaw.Serialize(), tokenRaw.Body.Id.ToUuid()); - - args.SessionToken = token; + args.SessionToken ??= await GetDefaultSession(args, ctx).ConfigureAwait(false); var payloadStream = args.Payload!; var header = args.Header!; - var fullLength = header.PayloadLength; - - if (payloadStream.CanSeek && fullLength == 0) - fullLength = (ulong)payloadStream.Length; - - args.FullLength = fullLength; + if (header.PayloadLength > 0) + args.FullLength = header.PayloadLength; + else if (payloadStream.CanSeek) + args.FullLength = (ulong)payloadStream.Length; + else + throw new ArgumentException("The stream does not have a length and payload length is not defined"); if (args.MaxObjectSizeCache == 0) { @@ -456,12 +439,12 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; } - var restBytes = fullLength - args.CurrentStreamPosition; + var restBytes = args.FullLength - args.CurrentStreamPosition; var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache; //define collection capacity var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; - var objectsCount = fullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; + var objectsCount = args.FullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; List sentObjectIds = new(objectsCount); @@ -491,7 +474,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, [.. attributes]) { - PayloadLength = fullLength, + PayloadLength = args.FullLength, }; args.Header.Split!.ParentHeader = largeObjectHeader; @@ -526,8 +509,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl private async Task PutStreamObject(PrmObjectPut args) { var ctx = args.Context!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); var payload = args.Payload!; @@ -542,21 +523,26 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl try { + // 0 means no limit from client, so server side cut is performed + var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; + if (args.CustomBuffer != null) { + if (args.CustomBuffer.Length < chunkSize) + { + throw new ArgumentException($"Buffer size is too small. At least {chunkSize} required"); + } + chunkBuffer = args.CustomBuffer; } else { - chunkBuffer = ClientContext.GetArrayPool(Constants.ObjectChunkSize).Rent(chunkSize); + chunkBuffer = ArrayPool.Shared.Rent(chunkSize); isRentBuffer = true; } var sentBytes = 0; - // 0 means no limit from client, so server side cut is performed - var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; - using var stream = await GetUploadStream(args, ctx).ConfigureAwait(false); while (objectLimitSize == 0 || sentBytes < objectLimitSize) @@ -581,7 +567,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - chunkRequest.Sign(ctx.Key); + chunkRequest.Sign(ClientContext.Key.ECDsaKey); await stream.Write(chunkRequest).ConfigureAwait(false); } @@ -589,7 +575,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var response = await stream.Close().ConfigureAwait(false); Verifier.CheckResponse(response); - return new PutObjectResult(FrostFsObjectId.FromHash(response.Body.ObjectId.Value.ToByteArray()), sentBytes); + return new PutObjectResult(FrostFsObjectId.FromHash(response.Body.ObjectId.Value.Span), sentBytes); } finally { @@ -604,17 +590,14 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var header = args.Header!; - if (ctx.Key == null) - throw new ArgumentNullException(nameof(args), "Key is null"); - - header.OwnerId ??= ctx.OwnerId; - header.Version ??= ctx.Version; + header.OwnerId ??= ClientContext.Owner; + header.Version ??= ClientContext.Version; var grpcHeader = header.GetHeader(); if (header.Split != null) { - ObjectTools.SetSplitValues(grpcHeader, header.Split, ctx); + ObjectTools.SetSplitValues(grpcHeader, header.Split, ClientContext); } var oid = new ObjectID { Value = grpcHeader.Sha256() }; @@ -630,17 +613,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; - var sessionToken = await GetOrCreateSession(args, ctx).ConfigureAwait(false); + var sessionToken = (args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false)); - sessionToken.CreateObjectTokenContext( + var protoToken = sessionToken.CreateObjectTokenContext( new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid }, ObjectSessionContext.Types.Verb.Put, - ctx.Key - ); + ClientContext.Key); - initRequest.AddMetaHeader(args.XHeaders, sessionToken); + initRequest.AddMetaHeader(args.XHeaders, protoToken); - initRequest.Sign(ctx.Key); + initRequest.Sign(ClientContext.Key.ECDsaKey); return await PutObjectInit(initRequest, ctx).ConfigureAwait(false); } @@ -681,24 +663,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return new ObjectReader(call); } - private async IAsyncEnumerable SearchObjects(SearchRequest request, CallContext ctx) - { - using var stream = GetSearchReader(request, ctx); - - while (true) - { - var ids = await stream.Read(ctx.CancellationToken).ConfigureAwait(false); - - if (ids == null) - break; - - foreach (var oid in ids) - { - yield return oid; - } - } - } - private SearchReader GetSearchReader(SearchRequest initRequest, CallContext ctx) { if (initRequest is null) diff --git a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs index e9b5963..859d836 100644 --- a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs @@ -1,7 +1,5 @@ using System.Threading.Tasks; -using FrostFS.SDK.Client; -using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.Session; namespace FrostFS.SDK.Client; @@ -20,19 +18,17 @@ internal sealed class SessionServiceProvider : ContextAccessor { var ctx = args.Context!; - ctx.OwnerId ??= ClientContext.Owner; - var request = new CreateRequest { Body = new CreateRequest.Types.Body { - OwnerId = ctx.OwnerId!.ToMessage(), + OwnerId = ClientContext.Owner.OwnerID, Expiration = args.Expiration } }; request.AddMetaHeader(args.XHeaders); - request.Sign(ctx.Key!); + request.Sign(ClientContext.Key.ECDsaKey); return await CreateSession(request, args.Context!).ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs index a8d80ac..e8ff793 100644 --- a/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs @@ -9,14 +9,15 @@ internal interface ISessionProvider internal sealed class SessionProvider(ClientContext envCtx) { - public async ValueTask GetOrCreateSession(ISessionToken args, CallContext ctx) + public async Task CreateSession(ISessionToken args, CallContext ctx) { - if (args.SessionToken is null) - { - return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)) - .ConfigureAwait(false); - } + var token = await GetDefaultSession(args, ctx).ConfigureAwait(false); - return new Session.SessionToken().Deserialize(args.SessionToken.Token); + return new FrostFsSessionToken(token); + } + + internal async Task GetDefaultSession(ISessionToken args, CallContext ctx) + { + return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Tools/ClientContext.cs b/src/FrostFS.SDK.Client/Tools/ClientContext.cs index 5f7c72e..133ad3f 100644 --- a/src/FrostFS.SDK.Client/Tools/ClientContext.cs +++ b/src/FrostFS.SDK.Client/Tools/ClientContext.cs @@ -1,19 +1,16 @@ using System; -using System.Buffers; -using System.Security.Cryptography; - -using FrostFS.SDK.Cryptography; +using System.Collections.ObjectModel; +using Grpc.Core.Interceptors; using Grpc.Net.Client; namespace FrostFS.SDK.Client; -public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner, GrpcChannel channel, FrostFsVersion version) : IDisposable +public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner owner, GrpcChannel channel, FrostFsVersion version) : IDisposable { - private ArrayPool? _arrayPool; private string? sessionKey; - internal FrostFsOwner? Owner { get; } = owner; + internal FrostFsOwner Owner { get; } = owner; internal string? Address { get; } = channel.Target; @@ -25,9 +22,16 @@ public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner internal FrostFSClient Client { get; } = client; - internal ClientKey? Key { get; } = key != null ? new ClientKey(key) : null; + internal ClientKey Key { get; } = key; + + internal SessionCache? SessionCache { get; set; } + + internal Action? Callback { get; set; } + + internal Collection? Interceptors { get; set; } + + internal Action? PoolErrorHandler { get; set; } - internal SessionCache SessionCache { get; set; } internal string? SessionCacheKey { @@ -35,23 +39,13 @@ public class ClientContext(FrostFSClient client, ECDsa? key, FrostFsOwner? owner { if (sessionKey == null && Key != null && Address != null) { - sessionKey = Pool.FormCacheKey(Address, Key.ECDsaKey.PrivateKey().ToString()); + sessionKey = Pool.FormCacheKey(Address, Key.PublicKey); } return sessionKey; } } - /// - /// Custom pool is used for predefined sizes of buffers like grpc chunk - /// - internal ArrayPool GetArrayPool(int size) - { - _arrayPool ??= ArrayPool.Create(size, 256); - - return _arrayPool; - } - public void Dispose() { Dispose(true); diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index f20cacd..eeb06c0 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -11,7 +11,7 @@ namespace FrostFS.SDK.Client; internal static class ObjectTools { - internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx) + internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, ClientContext ctx) { var grpcHeader = CreateHeader(header, [], ctx); @@ -21,9 +21,9 @@ internal static class ObjectTools return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal static Object.Object CreateObject(FrostFsObject @object, CallContext ctx) + internal static Object.Object CreateObject(FrostFsObject @object, ClientContext ctx) { - @object.Header.OwnerId ??= ctx.OwnerId; + @object.Header.OwnerId ??= ctx.Owner; @object.Header.Version ??= ctx.Version; var grpcHeader = @object.Header.GetHeader(); @@ -46,14 +46,14 @@ internal static class ObjectTools obj.Signature = new Signature { - Key = ctx.GetPublicKeyCache(), - Sign = ByteString.CopyFrom(ctx.Key!.SignData(obj.ObjectId.ToByteArray())), + Key = ctx.Key.PublicKeyProto, + Sign = ctx.Key.ECDsaKey.SignData(obj.ObjectId.ToByteArray()), }; return obj; } - internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, CallContext ctx) + internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, ClientContext ctx) { if (split == null) return; @@ -77,17 +77,17 @@ internal static class ObjectTools grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Signature { - Key = ctx.GetPublicKeyCache(), - Sign = ByteString.CopyFrom(ctx.Key.SignData(grpcHeader.Split.Parent.ToByteArray())), + Key = ctx.Key.PublicKeyProto, + Sign = ctx.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray()), }; } grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, CallContext ctx) + internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, ClientContext ctx) { - header.OwnerId ??= ctx.OwnerId; + header.OwnerId ??= ctx.Owner; header.Version ??= ctx.Version; var grpcHeader = header.GetHeader(); diff --git a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs index 65d9e35..64aecc6 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Specialized; using System.Linq; -using System.Security.Cryptography; -using FrostFS.Refs; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using FrostFS.SDK.Proto.Interfaces; using FrostFS.Session; @@ -33,59 +30,4 @@ public static class RequestConstructor xHeaders.Cast().SelectMany(key => xHeaders.GetValues(key), (k, v) => new XHeader { Key = k, Value = v })); } - - public static void CreateObjectTokenContext(this SessionToken sessionToken, - Address address, - ObjectSessionContext.Types.Verb verb, - ECDsa key) - { - if (sessionToken is null) - { - throw new ArgumentNullException(nameof(sessionToken)); - } - - if (address is null) - { - throw new ArgumentNullException(nameof(address)); - } - - if (sessionToken.Body.Object?.Target != null) - return; - - ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId }; - - if (address.ObjectId != null) - target.Objects.Add(address.ObjectId); - - sessionToken.Body.Object = new() - { - Target = target, - Verb = verb - }; - - sessionToken.Body.SessionKey = Google.Protobuf.ByteString.CopyFrom(key.PublicKey()); - - sessionToken.Signature = key.SignMessagePart(sessionToken.Body); - } - - internal static void CreateContainerTokenContext(this SessionToken sessionToken, - ContainerID? containerId, - ContainerSessionContext.Types.Verb verb, - ECDsa key, - Google.Protobuf.ByteString publicKey) - { - if (sessionToken.Body.Container?.ContainerId != null) - return; - - sessionToken.Body.Container = new() { Verb = verb }; - - if (containerId != null) - sessionToken.Body.Container.ContainerId = containerId; - else - sessionToken.Body.Container.Wildcard = true; - - sessionToken.Body.SessionKey = publicKey; - - sessionToken.Signature = key.SignMessagePart(sessionToken.Body); - } } diff --git a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs index a45c569..f747b3f 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -14,24 +14,21 @@ using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; +using Signature = FrostFS.Refs.Signature; + namespace FrostFS.SDK.Client; public static class RequestSigner { internal const int RFC6979SignatureSize = 64; - internal static byte[] SignRFC6979(this ECDsa key, byte[] data) + internal static ByteString SignRFC6979(this ECDsa key, byte[] data) { if (key is null) { throw new ArgumentNullException(nameof(key)); } - if (data is null) - { - throw new ArgumentNullException(nameof(data)); - } - var digest = new Sha256Digest(); var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); @@ -44,24 +41,27 @@ public static class RequestSigner signer.Init(true, privateKey); var rs = signer.GenerateSignature(hash); - var signature = new byte[RFC6979SignatureSize]; + + Span signature = stackalloc byte[RFC6979SignatureSize]; + var rbytes = rs[0].ToByteArrayUnsigned(); var sbytes = rs[1].ToByteArrayUnsigned(); var index = RFC6979SignatureSize / 2 - rbytes.Length; - rbytes.CopyTo(signature, index); + rbytes.AsSpan().CopyTo(signature[index..]); index = RFC6979SignatureSize - sbytes.Length; - sbytes.CopyTo(signature, index); + sbytes.AsSpan().CopyTo(signature[index..]); - return signature; + return ByteString.CopyFrom(signature); } + internal static SignatureRFC6979 SignRFC6979(this ECDsa key, IMessage message) { return new SignatureRFC6979 { Key = ByteString.CopyFrom(key.PublicKey()), - Sign = ByteString.CopyFrom(key.SignRFC6979(message.ToByteArray())), + Sign = key.SignRFC6979(message.ToByteArray()), }; } @@ -70,23 +70,26 @@ public static class RequestSigner return new SignatureRFC6979 { Key = ByteString.CopyFrom(key.PublicKey()), - Sign = ByteString.CopyFrom(key.SignRFC6979(data.ToByteArray())), + Sign = key.SignRFC6979(data.ToByteArray()), }; } - public static byte[] SignData(this ECDsa key, byte[] data) + public static ByteString SignData(this ECDsa key, byte[] data) { if (key is null) { throw new ArgumentNullException(nameof(key)); } - var hash = new byte[65]; - hash[0] = 0x04; + Span result = stackalloc byte[65]; + result[0] = 0x04; - key.SignHash(data.Sha512()).CopyTo(hash, 1); + //var hash = new byte[65]; + //hash[0] = 0x04; - return hash; + key.SignHash(data.Sha512()).AsSpan().CopyTo(result[1..]); + + return ByteString.CopyFrom(result); } internal static Signature SignMessagePart(this ECDsa key, IMessage? data) @@ -95,7 +98,7 @@ public static class RequestSigner var sig = new Signature { Key = ByteString.CopyFrom(key.PublicKey()), - Sign = ByteString.CopyFrom(key.SignData(data2Sign)), + Sign = key.SignData(data2Sign), }; return sig; diff --git a/src/FrostFS.SDK.Client/Tools/SearchReader.cs b/src/FrostFS.SDK.Client/Tools/SearchReader.cs index c2e7607..ba9b1d1 100644 --- a/src/FrostFS.SDK.Client/Tools/SearchReader.cs +++ b/src/FrostFS.SDK.Client/Tools/SearchReader.cs @@ -1,21 +1,21 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using FrostFS.Object; using FrostFS.Refs; +using Google.Protobuf.Collections; + using Grpc.Core; namespace FrostFS.SDK.Client; internal sealed class SearchReader(AsyncServerStreamingCall call) : IDisposable { - public AsyncServerStreamingCall Call { get; private set; } = call; + internal AsyncServerStreamingCall Call { get; private set; } = call; - public async Task?> Read(CancellationToken cancellationToken) + internal async Task?> Read(CancellationToken cancellationToken) { if (!await Call.ResponseStream.MoveNext(cancellationToken).ConfigureAwait(false)) return null; @@ -24,7 +24,7 @@ internal sealed class SearchReader(AsyncServerStreamingCall call Verifier.CheckResponse(response); - return response.Body?.IdList.ToList(); + return response.Body?.IdList; } public void Dispose() diff --git a/src/FrostFS.SDK.Client/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs index bc8dd31..ce215ba 100644 --- a/src/FrostFS.SDK.Client/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -88,6 +88,7 @@ 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)) diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 148ae78..604fe7c 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -7,12 +7,12 @@ - true + true - + - true - + true + diff --git a/src/FrostFS.SDK.Cryptography/UUID.cs b/src/FrostFS.SDK.Cryptography/UUID.cs index c92a874..94fba3f 100644 --- a/src/FrostFS.SDK.Cryptography/UUID.cs +++ b/src/FrostFS.SDK.Cryptography/UUID.cs @@ -11,9 +11,7 @@ public static class UUIDExtension if (id == null) throw new ArgumentNullException(nameof(id)); - var bytes = id.ToByteArray(); - - var orderedBytes = GetGuidBytesDirectOrder(bytes); + var orderedBytes = GetGuidBytesDirectOrder(id.Span); return new Guid(orderedBytes); } @@ -32,7 +30,7 @@ public static class UUIDExtension return orderedBytes; } - private static byte[] GetGuidBytesDirectOrder(byte[] source) + private static byte[] GetGuidBytesDirectOrder(ReadOnlySpan source) { if (source.Length != 16) throw new ArgumentException("Wrong uuid binary format"); diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 0bf48d5..8f69fe0 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -7,11 +7,11 @@ - true + true - + - true + true diff --git a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs index 55f548d..3a0d396 100644 --- a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs +++ b/src/FrostFS.SDK.Tests/ContainerTestsBase.cs @@ -8,12 +8,12 @@ public abstract class ContainerTestsBase { internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + protected IOptions Settings { get; set; } protected ContainerMocker Mocker { get; set; } protected ContainerTestsBase() { - Settings = Options.Create(new SingleOwnerClientSettings + Settings = Options.Create(new ClientSettings { Key = key, Host = "http://localhost:8080" diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj index d5cb78f..30c0564 100644 --- a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -10,15 +10,15 @@ - true + true - + - true + true - + - + @@ -38,7 +38,7 @@ - + PreserveNewest diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs index d1366c7..bd9d858 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamRangeReaderMock.cs @@ -1,9 +1,4 @@ -using System.Security.Cryptography; - using FrostFS.Object; -using FrostFS.SDK.Client; -using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using FrostFS.Session; using Google.Protobuf; diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index c09df83..9e9a18d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -43,7 +43,7 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) Signature = new Refs.Signature { Key = ByteString.CopyFrom(Key.PublicKey()), - Sign = ByteString.CopyFrom(Key.SignData(header.ToByteArray())), + Sign = Key.SignData(header.ToByteArray()), } } }, diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index ca1c670..8d4230c 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -46,19 +46,19 @@ public abstract class ServiceBase(string key) { Key = ByteString.CopyFrom(Key.PublicKey()), Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.MetaHeader.ToByteArray())) + Sign = Key.SignData(response.MetaHeader.ToByteArray()) }, BodySignature = new Refs.Signature { Key = ByteString.CopyFrom(Key.PublicKey()), Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData(response.GetBody().ToByteArray())) + Sign = Key.SignData(response.GetBody().ToByteArray()) }, OriginSignature = new Refs.Signature { Key = ByteString.CopyFrom(Key.PublicKey()), Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = ByteString.CopyFrom(Key.SignData([])) + Sign = Key.SignData([]) } }; diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 4b2b5c1..4f07e41 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -5,7 +5,6 @@ using FrostFS.Object; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; -using FrostFS.Session; using Google.Protobuf; @@ -41,7 +40,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) public GetRangeHashRequest? GetRangeHashRequest { get; set; } - public Collection RangeHashResponses { get; } = []; + public Collection RangeHashResponses { get; } = []; public override Mock GetMock() { @@ -94,7 +93,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) headResponse.Body.Header.Signature = new Refs.Signature { Key = ByteString.CopyFrom(Key.PublicKey()), - Sign = ByteString.CopyFrom(Key.SignData(headResponse.Body.Header.ToByteArray())), + Sign = Key.SignData(headResponse.Body.Header.ToByteArray()), }; headResponse.VerifyHeader = GetResponseVerificationHeader(headResponse); @@ -259,7 +258,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) response.Body.HashList.Add(hash); } } - + response.VerifyHeader = GetResponseVerificationHeader(response); return new AsyncUnaryCall( @@ -281,7 +280,7 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) { Body = new PatchResponse.Types.Body { - ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData([1,2,3])) }, + ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData([1, 2, 3])) }, }, MetaHeader = ResponseMetaHeader }; diff --git a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs index 88ed60d..ebea821 100644 --- a/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/SessionMock.cs @@ -31,7 +31,7 @@ public class SessionMocker(string key) : ServiceBase(key) if (SessionKey == null) { - SessionKey = new byte[32]; + SessionKey = new byte[33]; rand.NextBytes(SessionKey); } diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/NetworkTest.cs index 2123460..9e83bb0 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/NetworkTest.cs @@ -32,16 +32,13 @@ public class NetworkTest : NetworkTestsBase new PrmNetworkSettings(new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20), - OwnerId = OwnerId, - Key = ECDsaKey, - Version = Version + Timeout = TimeSpan.FromSeconds(20) }) : new PrmNetworkSettings(); var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient().GetNetworkSettingsAsync(param); + var result = await GetClient(DefaultSettings).GetNetworkSettingsAsync(param); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -113,17 +110,14 @@ public class NetworkTest : NetworkTestsBase Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; - PrmNetmapSnapshot param; + PrmNetmapSnapshot param = new(); if (useContext) { var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20), - OwnerId = OwnerId, - Key = ECDsaKey, - Version = Version + Timeout = TimeSpan.FromSeconds(20) }; param = new(ctx); @@ -137,7 +131,7 @@ public class NetworkTest : NetworkTestsBase var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient().GetNetmapSnapshotAsync(param); + var result = await GetClient(DefaultSettings).GetNetmapSnapshotAsync(param); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -219,10 +213,7 @@ public class NetworkTest : NetworkTestsBase var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20), - OwnerId = OwnerId, - Key = ECDsaKey, - Version = Version + Timeout = TimeSpan.FromSeconds(20) }; param = new(ctx); @@ -236,7 +227,7 @@ public class NetworkTest : NetworkTestsBase var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient().GetNodeInfoAsync(param); + var result = await GetClient(DefaultSettings).GetNodeInfoAsync(param); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); diff --git a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs index 8090ecd..0cabab5 100644 --- a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs +++ b/src/FrostFS.SDK.Tests/NetworkTestsBase.cs @@ -1,8 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; using FrostFS.SDK.Client.Interfaces; -using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; @@ -13,32 +11,27 @@ public abstract class NetworkTestsBase { internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + // protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13); - protected FrostFsVersion Version { get; set; } = new FrostFsVersion(2, 13); - - protected ECDsa ECDsaKey { get; set; } - protected FrostFsOwner OwnerId { get; set; } protected NetworkMocker Mocker { get; set; } + protected ClientSettings DefaultSettings { get; } + protected NetworkTestsBase() { - Settings = Options.Create(new SingleOwnerClientSettings + DefaultSettings = new ClientSettings { Key = key, - Host = "http://localhost:8080" - }); - - ECDsaKey = key.LoadWif(); - OwnerId = FrostFsOwner.FromKey(ECDsaKey); + Host = "http://localhost:8080", + }; Mocker = new NetworkMocker(this.key); } - protected IFrostFSClient GetClient() + protected IFrostFSClient GetClient(ClientSettings settings) { return Client.FrostFSClient.GetTestInstance( - Settings, + Options.Create(settings), null, Mocker.GetMock().Object, new SessionMocker(this.key).GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/ObjectTest.cs index dabbbc8..5914605 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/ObjectTest.cs @@ -6,12 +6,9 @@ using System.Text; using FrostFS.Refs; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using Google.Protobuf; -using static FrostFS.Object.ECInfo.Types; - namespace FrostFS.SDK.Tests; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] @@ -23,18 +20,9 @@ public class ObjectTest : ObjectTestsBase { var client = GetClient(); - var ecdsaKey = key.LoadWif(); + var objectId = client.CalculateObjectId(Mocker.ObjectHeader!); - var ctx = new CallContext - { - Key = ecdsaKey, - OwnerId = FrostFsOwner.FromKey(ecdsaKey), - Version = new FrostFsVersion(2, 13) - }; - - var objectId = client.CalculateObjectId(Mocker.ObjectHeader!, ctx); - - var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId, ctx)); + var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId)); Assert.NotNull(result); @@ -90,7 +78,7 @@ public class ObjectTest : ObjectTestsBase NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]); var blockSize = 2560; - byte[] bytes = File.ReadAllBytes(@".\..\..\..\TestData\cat.jpg"); + byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); var fileLength = bytes.Length; var param = new PrmObjectPut @@ -295,7 +283,7 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(SHA256.HashData(hash), SHA256.HashData(result.First().ToArray())); } - + [Fact] public async void PatchTest() { @@ -337,9 +325,9 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(address.ContainerId, body.Address.ContainerId); Assert.Equal(address.ObjectId, body.Address.ObjectId); - + Assert.Equal(32, body.Patch.Chunk.Length); - + Assert.Equal(SHA256.HashData(patch), SHA256.HashData(body.Patch.Chunk.ToArray())); } } diff --git a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs index ba68e6f..21289e4 100644 --- a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/ObjectTestsBase.cs @@ -10,7 +10,7 @@ public abstract class ObjectTestsBase { protected static readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + protected IOptions Settings { get; set; } protected FrostFsContainerId ContainerId { get; set; } protected NetworkMocker NetworkMocker { get; set; } = new NetworkMocker(key); @@ -22,7 +22,7 @@ public abstract class ObjectTestsBase { var ecdsaKey = key.LoadWif(); - Settings = Options.Create(new SingleOwnerClientSettings + Settings = Options.Create(new ClientSettings { Key = key, Host = "http://localhost:8080" diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs index efc4802..0a6f2b3 100644 --- a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/PoolSmokeTests.cs @@ -6,8 +6,6 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -using static FrostFS.Session.SessionToken.Types.Body; - namespace FrostFS.SDK.SmokeTests; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] @@ -78,6 +76,8 @@ public class PoolSmokeTests : SmokeTestsBase [Fact] public async void NodeInfoStatisticsTwoNodesTest() { + var callbackText = string.Empty; + var options = new InitParameters { Key = keyString.LoadWif(), @@ -87,16 +87,14 @@ public class PoolSmokeTests : SmokeTestsBase ], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, - Logger = null + Logger = null, + Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" }; using var pool = new Pool(options); - var callbackText = string.Empty; - var ctx = new CallContext { - Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" }; var error = await pool.Dial(ctx).ConfigureAwait(true); @@ -118,14 +116,12 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); + var callbackText = string.Empty; + options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; + using var pool = new Pool(options); - var callbackText = string.Empty; - - var ctx = new CallContext - { - Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" - }; + var ctx = new CallContext(); var error = await pool.Dial(ctx).ConfigureAwait(true); @@ -154,18 +150,11 @@ public class PoolSmokeTests : SmokeTestsBase var token = await pool.CreateSessionAsync(prm).ConfigureAwait(true); - var session = new Session.SessionToken().Deserialize(token.Token); - var ownerHash = Base58.Decode(OwnerId!.Value); - Assert.NotNull(session); - Assert.Null(session.Body.Container); - Assert.Null(session.Body.Object); - Assert.Equal(16, session.Body.Id.Length); - Assert.Equal(100ul, session.Body.Lifetime.Exp); - Assert.Equal(ownerHash, session.Body.OwnerId.Value); - Assert.Equal(33, session.Body.SessionKey.Length); - Assert.Equal(ContextOneofCase.None, session.Body.ContextCase); + Assert.NotNull(token); + Assert.NotEqual(Guid.Empty, token.Id); + Assert.Equal(33, token.SessionKey.Length); } [Fact] @@ -314,8 +303,16 @@ public class PoolSmokeTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { + bool callbackInvoked = false; + var options = GetDefaultParams(); + options.Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }); + using var pool = new Pool(options); var error = await pool.Dial(new CallContext()).ConfigureAwait(true); @@ -324,16 +321,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); - bool callbackInvoked = false; - var ctx = new CallContext - { - // Timeout = TimeSpan.FromSeconds(20), - Callback = new((CallStatistics cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }) - }; + var ctx = new CallContext { }; var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); @@ -346,12 +334,7 @@ public class PoolSmokeTests : SmokeTestsBase var bytes = GetRandomBytes(objectSize); - var ctx1 = new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }; - - var param = new PrmObjectPut(ctx1) + var param = new PrmObjectPut() { Header = new FrostFsObjectHeader( containerId: createdContainer, @@ -410,6 +393,7 @@ public class PoolSmokeTests : SmokeTestsBase var options = GetDefaultParams(); using var pool = new Pool(options); + options.Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); var error = await pool.Dial(new CallContext()).ConfigureAwait(true); @@ -421,8 +405,7 @@ public class PoolSmokeTests : SmokeTestsBase var ctx = new CallContext { - Timeout = TimeSpan.FromSeconds(20), - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + Timeout = TimeSpan.FromSeconds(20) }; var createContainerParam = new PrmContainerCreate( @@ -435,7 +418,7 @@ public class PoolSmokeTests : SmokeTestsBase var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut(new CallContext { Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) }) + var param = new PrmObjectPut(new CallContext()) { Header = new FrostFsObjectHeader( containerId: container, @@ -517,7 +500,7 @@ public class PoolSmokeTests : SmokeTestsBase Timeout = TimeSpan.FromSeconds(10), }; - ctx.Interceptors.Add(new CallbackInterceptor()); + // ctx.Interceptors.Add(new CallbackInterceptor()); var container = await pool.GetContainerAsync(new PrmContainerGet(containerId, ctx)); @@ -585,9 +568,9 @@ public class PoolSmokeTests : SmokeTestsBase return bytes; } - private static IOptions GetSingleOwnerOptions(string key, string url) + private static IOptions GetSingleOwnerOptions(string key, string url) { - return Options.Create(new SingleOwnerClientSettings + return Options.Create(new ClientSettings { Key = key, Host = url diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/SessionTests.cs index 4cf03ba..9e79b29 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/SessionTests.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; +using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.Tests; @@ -21,10 +22,7 @@ public class SessionTest : SessionTestsBase var ctx = new CallContext { CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20), - OwnerId = OwnerId, - Key = ECDsaKey, - Version = Mocker.Version + Timeout = TimeSpan.FromSeconds(20) }; param = new PrmSessionCreate(exp, ctx); @@ -43,18 +41,16 @@ public class SessionTest : SessionTestsBase var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); Assert.NotNull(result); - Assert.NotNull(result.Token); + Assert.NotEqual(Guid.Empty, result.Id); - var session = new Session.SessionToken().Deserialize(result.Token); + Assert.Equal(Mocker.SessionId, result.Id.ToBytes()); + Assert.Equal(Mocker.SessionKey, result.SessionKey.ToArray()); - Assert.Equal(Mocker.SessionId, session.Body.Id); - Assert.Equal(Mocker.SessionKey, session.Body.SessionKey); - - Assert.Equal(OwnerId.ToMessage(), session.Body.OwnerId); - Assert.Equal(exp, session.Body.Lifetime.Exp); - Assert.Equal(exp, session.Body.Lifetime.Iat); - Assert.Equal(exp, session.Body.Lifetime.Nbf); - Assert.Null(session.Body.Container); + //Assert.Equal(OwnerId.ToMessage(), result.Token.Body.OwnerId); + //Assert.Equal(exp, result.Token.Body.Lifetime.Exp); + //Assert.Equal(exp, result.Token.Body.Lifetime.Iat); + //Assert.Equal(exp, result.Token.Body.Lifetime.Nbf); + //Assert.Null(result.Token.Body.Container); Assert.NotNull(Mocker.CreateSessionRequest); diff --git a/src/FrostFS.SDK.Tests/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/SessionTestsBase.cs index 93bc140..bd958fc 100644 --- a/src/FrostFS.SDK.Tests/SessionTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SessionTestsBase.cs @@ -11,7 +11,7 @@ public abstract class SessionTestsBase { internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; - protected IOptions Settings { get; set; } + protected IOptions Settings { get; set; } protected ECDsa ECDsaKey { get; set; } protected FrostFsOwner OwnerId { get; set; } @@ -19,7 +19,7 @@ public abstract class SessionTestsBase protected SessionTestsBase() { - Settings = Options.Create(new SingleOwnerClientSettings + Settings = Options.Create(new ClientSettings { Key = key, Host = "http://localhost:8080" diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/SmokeClientTests.cs index ba822c7..aaf1869 100644 --- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/SmokeClientTests.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using FrostFS.SDK.Client; @@ -7,8 +7,6 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -using static FrostFS.Session.SessionToken.Types.Body; - namespace FrostFS.SDK.SmokeTests; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] @@ -17,29 +15,23 @@ public class SmokeClientTests : SmokeTestsBase { private static readonly PrmWait lightWait = new(100, 1); - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void AccountTest(bool isSingleOnwerClient) + [Fact] + public async void AccountTest() { - using var client = isSingleOnwerClient - ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) - : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); - PrmBalance? prm = isSingleOnwerClient ? default : new(Ctx); + var result = await client.GetBalanceAsync(); - var result = await client.GetBalanceAsync(prm); + Assert.NotNull(result); + Assert.True(result.Value == 0); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void NetworkMapTest(bool isSingleOnwerClient) + [Fact] + public async void NetworkMapTest() { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); - PrmNetmapSnapshot? prm = isSingleOnwerClient ? default : new(Ctx); - var result = await client.GetNetmapSnapshotAsync(prm); + var result = await client.GetNetmapSnapshotAsync(); Assert.True(result.Epoch > 0); Assert.Single(result.NodeInfoCollection); @@ -54,18 +46,12 @@ public class SmokeClientTests : SmokeTestsBase } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void NodeInfoTest(bool isSingleOnwerClient) + [Fact] + public async void NodeInfoTest() { - using var client = isSingleOnwerClient - ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) - : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); - PrmNodeInfo? prm = isSingleOnwerClient ? default : new(Ctx); - - var result = await client.GetNodeInfoAsync(prm); + var result = await client.GetNodeInfoAsync(); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); @@ -78,45 +64,38 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoStatisticsTest() { - var ctx = new CallContext - { - Callback = (cs) => Console.WriteLine($"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds") - }; + var options = GetClientOptions(this.keyString, this.url); - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + var callbackContent = string.Empty; + options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; + + using var client = FrostFSClient.GetSingleOwnerInstance(options); var result = await client.GetNodeInfoAsync(); + + Assert.NotEmpty(callbackContent); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void GetSessionTest(bool isSingleOnwerClient) + [Fact] + public async void GetSessionTest() { - using var client = isSingleOnwerClient ? FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)) : FrostFSClient.GetInstance(GetOptions(this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); - PrmSessionCreate? prm = isSingleOnwerClient ? new PrmSessionCreate(100) : new PrmSessionCreate(100, Ctx); + PrmSessionCreate? prm = new(100); var token = await client.CreateSessionAsync(prm); - var session = new Session.SessionToken().Deserialize(token.Token); - var ownerHash = Base58.Decode(OwnerId!.Value); - Assert.NotNull(session); - Assert.Null(session.Body.Container); - Assert.Null(session.Body.Object); - Assert.Equal(16, session.Body.Id.Length); - Assert.Equal(100ul, session.Body.Lifetime.Exp); - Assert.Equal(ownerHash, session.Body.OwnerId.Value); - Assert.Equal(33, session.Body.SessionKey.Length); - Assert.Equal(ContextOneofCase.None, session.Body.ContextCase); + Assert.NotNull(token); + Assert.NotEqual(Guid.Empty, token.Id); + Assert.Equal(33, token.SessionKey.Length); } [Fact] public async void CreateObjectWithSessionToken() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -164,7 +143,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void FilterTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -248,89 +227,87 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + bool callbackInvoked = false; - await Cleanup(client); + var options = GetClientOptions(this.keyString, this.url); - bool callbackInvoked = false; - var ctx = new CallContext - { - // Timeout = TimeSpan.FromSeconds(20), - Callback = new((CallStatistics cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }) - }; + options.Value.Callback = new((CallStatistics cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }); - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); + using var client = FrostFSClient.GetSingleOwnerInstance(options); - var createdContainer = await client.CreateContainerAsync(createContainerParam); + await Cleanup(client); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer, ctx)); - Assert.NotNull(container); - Assert.True(callbackInvoked); + var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(20) }; - var bytes = GetRandomBytes(objectSize); + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); - var param = new PrmObjectPut(new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }) - { - Header = new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + var createdContainer = await client.CreateContainerAsync(createContainerParam); - var objectId = await client.PutObjectAsync(param); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + Assert.NotNull(container); + Assert.True(callbackInvoked); - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + var bytes = GetRandomBytes(objectSize); - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) - { - hasObject = true; + var param = new PrmObjectPut + { + Header = new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + Payload = new MemoryStream(bytes), + ClientCut = false + }; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } + var objectId = await client.PutObjectAsync(param); - Assert.True(hasObject); + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + { + hasObject = true; - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } + Assert.True(hasObject); - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); - await Cleanup(client); + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); - await foreach (var _ in client.ListContainersAsync()) - { - Assert.Fail("Containers exist"); - } + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync()) + { + Assert.Fail("Containers exist"); + } } [Fact] public async void PatchTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -388,7 +365,7 @@ public class SmokeClientTests : SmokeTestsBase ms.Write(chunk.Value.Span); } - for(int i = 0; i < (int)range.Offset; i++) + for (int i = 0; i < (int)range.Offset; i++) Assert.Equal(downloadedBytes[i], bytes[i]); var rangeEnd = range.Offset + range.Length; @@ -410,7 +387,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -465,7 +442,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeHashTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -494,15 +471,15 @@ public class SmokeClientTests : SmokeTestsBase var objectId = await client.PutObjectAsync(param); - var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [ new FrostFsRange(100, 64)], bytes); + var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); var hashes = await client.GetRangeHashAsync(rangeParam); foreach (var hash in hashes) { - var x = hash.Slice(0, 32).ToArray(); + var x = hash[..32].ToArray(); } - + await Cleanup(client); await foreach (var _ in client.ListContainersAsync()) @@ -517,8 +494,8 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); - + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); await Cleanup(client); @@ -526,7 +503,7 @@ public class SmokeClientTests : SmokeTestsBase var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(20), - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + }; var createContainerParam = new PrmContainerCreate( @@ -539,10 +516,7 @@ public class SmokeClientTests : SmokeTestsBase var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut(new CallContext - { - Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - }) + var param = new PrmObjectPut { Header = new FrostFsObjectHeader( containerId: container, @@ -602,7 +576,11 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + + var options = GetClientOptions(this.keyString, this.url); + options.Value.Interceptors.Add(new CallbackInterceptor()); + + using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); await Cleanup(client); @@ -613,12 +591,7 @@ public class SmokeClientTests : SmokeTestsBase var containerId = await client.CreateContainerAsync(createContainerParam); - var ctx = new CallContext - { - Timeout = TimeSpan.FromSeconds(10) - }; - - ctx.Interceptors.Add(new CallbackInterceptor()); + var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(10) }; var container = await client.GetContainerAsync(new PrmContainerGet(containerId, ctx)); @@ -692,23 +665,21 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoCallbackAndInterceptorTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); - bool callbackInvoked = false; bool intercepterInvoked = false; - var ctx = new CallContext + var options = GetClientOptions(this.keyString, this.url); + options.Value.Callback = (CallStatistics cs) => { - Callback = new((CallStatistics cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }) + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); }; - ctx.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); + options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); - var result = await client.GetNodeInfoAsync(new PrmNodeInfo(ctx)); + using var client = FrostFSClient.GetSingleOwnerInstance(options); + + var result = await client.GetNodeInfoAsync(new PrmNodeInfo()); Assert.True(callbackInvoked); Assert.True(intercepterInvoked); @@ -729,22 +700,15 @@ public class SmokeClientTests : SmokeTestsBase return bytes; } - private static IOptions GetSingleOwnerOptions(string key, string url) + private static IOptions GetClientOptions(string key, string url) { - return Options.Create(new SingleOwnerClientSettings + return Options.Create(new ClientSettings { Key = key, Host = url }); } - private static IOptions GetOptions(string url) - { - return Options.Create(new ClientSettings - { - Host = url - }); - } static async Task Cleanup(IFrostFSClient client) { diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs index 5d67029..b96997c 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/SmokeTestsBase.cs @@ -7,8 +7,6 @@ namespace FrostFS.SDK.SmokeTests; public abstract class SmokeTestsBase { - // internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; - internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; internal readonly string url = "http://172.23.32.4:8080"; @@ -26,7 +24,5 @@ public abstract class SmokeTestsBase Key = keyString.LoadWif(); OwnerId = FrostFsOwner.FromKey(Key); Version = new FrostFsVersion(2, 13); - - Ctx = new CallContext { Key = Key, OwnerId = OwnerId, Version = Version }; } } diff --git a/src/FrostFS.SDK.Tests/TestData/cat.jpg b/src/FrostFS.SDK.Tests/cat.jpg similarity index 100% rename from src/FrostFS.SDK.Tests/TestData/cat.jpg rename to src/FrostFS.SDK.Tests/cat.jpg From 9bb7b5eff8437aed279c63d360ca8b39e833671d Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 2 Dec 2024 19:33:45 +0300 Subject: [PATCH 32/65] [#28] Clients: Make immutable parameters Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFSClient.cs | 193 ++--- .../Interfaces/IFrostFSClient.cs | 46 +- .../Models/Chain/ChainTarget.cs | 17 +- .../Models/Object/FrostFsHeaderResult.cs | 8 + .../Models/Object/FrostFsSplit.cs | 1 - .../Models/Object/FrostFsSplitInfo.cs | 26 + .../Parameters/CallContext.cs | 38 +- .../Parameters/Credentials.cs | 10 - src/FrostFS.SDK.Client/Parameters/IContext.cs | 11 - .../Parameters/ISessionToken.cs | 2 +- .../Parameters/PrmApeChainAdd.cs | 43 ++ .../Parameters/PrmApeChainList.cs | 36 +- .../Parameters/PrmApeChainRemove.cs | 40 +- .../Parameters/PrmApeRemoveAdd.cs | 8 - .../Parameters/PrmBalance.cs | 5 - src/FrostFS.SDK.Client/Parameters/PrmBase.cs | 14 - .../Parameters/PrmContainerCreate.cs | 52 +- .../Parameters/PrmContainerDelete.cs | 46 +- .../Parameters/PrmContainerGet.cs | 38 +- .../Parameters/PrmContainerGetAll.cs | 38 +- .../Parameters/PrmNetmapSnapshot.cs | 5 - .../Parameters/PrmNetworkSettings.cs | 5 - .../Parameters/PrmNodeInfo.cs | 5 - .../Parameters/PrmObjectDelete.cs | 48 +- .../Parameters/PrmObjectGet.cs | 49 +- .../Parameters/PrmObjectHeadGet.cs | 53 +- .../Parameters/PrmObjectPatch.cs | 61 +- .../Parameters/PrmObjectPut.cs | 62 +- .../Parameters/PrmObjectSearch.cs | 52 +- .../Parameters/PrmRangeGet.cs | 43 +- .../Parameters/PrmRangeHashGet.cs | 44 +- .../Parameters/PrmSessionCreate.cs | 38 +- .../Parameters/PrmSingleObjectPut.cs | 39 +- src/FrostFS.SDK.Client/Parameters/PrmWait.cs | 36 +- .../Parameters/PutObjectContext.cs | 10 + src/FrostFS.SDK.Client/Pool/ClientWrapper.cs | 12 +- src/FrostFS.SDK.Client/Pool/Pool.cs | 103 ++- .../Services/AccountingServiceProvider.cs | 8 +- .../Services/ApeManagerServiceProvider.cs | 18 +- .../Services/ContainerServiceProvider.cs | 34 +- .../Services/NetmapServiceProvider.cs | 20 +- .../Services/ObjectServiceProvider.cs | 125 ++- .../Services/SessionServiceProvider.cs | 8 +- .../Services/Shared/SessionProvider.cs | 2 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 10 - .../Tools/RequestConstructor.cs | 18 +- .../FrostFS.SDK.Cryptography.csproj | 2 +- src/FrostFS.SDK.Tests/GlobalSuppressions.cs | 6 - .../Multithread/MultiThreadTestsBase.cs | 42 ++ .../MultithreadPoolSmokeTests.cs} | 223 +++--- .../MultithreadSmokeClientTests.cs} | 323 ++++---- src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs | 585 +++++++++++++++ .../Smoke/SmokeClientTests.cs | 709 ++++++++++++++++++ .../{ => Smoke}/SmokeTestsBase.cs | 2 +- .../{ => Unit}/ContainerTest.cs | 12 +- .../{ => Unit}/ContainerTestsBase.cs | 10 +- .../{ => Unit}/NetworkTest.cs | 70 +- .../{ => Unit}/NetworkTestsBase.cs | 10 +- .../{ => Unit}/ObjectTest.cs | 98 +-- .../{ => Unit}/ObjectTestsBase.cs | 2 +- .../{ => Unit}/SessionTests.cs | 21 +- .../{ => Unit}/SessionTestsBase.cs | 10 +- 62 files changed, 2742 insertions(+), 963 deletions(-) create mode 100644 src/FrostFS.SDK.Client/Models/Object/FrostFsHeaderResult.cs create mode 100644 src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/Credentials.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/IContext.cs create mode 100644 src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmBalance.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmBase.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs delete mode 100644 src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs create mode 100644 src/FrostFS.SDK.Client/Parameters/PutObjectContext.cs delete mode 100644 src/FrostFS.SDK.Tests/GlobalSuppressions.cs create mode 100644 src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs rename src/FrostFS.SDK.Tests/{PoolSmokeTests.cs => Multithread/MultithreadPoolSmokeTests.cs} (72%) rename src/FrostFS.SDK.Tests/{SmokeClientTests.cs => Multithread/MultithreadSmokeClientTests.cs} (67%) create mode 100644 src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs create mode 100644 src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs rename src/FrostFS.SDK.Tests/{ => Smoke}/SmokeTestsBase.cs (94%) rename src/FrostFS.SDK.Tests/{ => Unit}/ContainerTest.cs (89%) rename src/FrostFS.SDK.Tests/{ => Unit}/ContainerTestsBase.cs (78%) rename src/FrostFS.SDK.Tests/{ => Unit}/NetworkTest.cs (81%) rename src/FrostFS.SDK.Tests/{ => Unit}/NetworkTestsBase.cs (79%) rename src/FrostFS.SDK.Tests/{ => Unit}/ObjectTest.cs (76%) rename src/FrostFS.SDK.Tests/{ => Unit}/ObjectTestsBase.cs (98%) rename src/FrostFS.SDK.Tests/{ => Unit}/SessionTests.cs (78%) rename src/FrostFS.SDK.Tests/{ => Unit}/SessionTestsBase.cs (81%) diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index e73ba49..4b42fab 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -49,7 +49,7 @@ public class FrostFSClient : IFrostFSClient internal ClientContext ClientCtx { get; set; } - public static IFrostFSClient GetSingleOwnerInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) + public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) { return new FrostFSClient(clientOptions, channelOptions); } @@ -170,229 +170,134 @@ public class FrostFSClient : IFrostFSClient } #region ApeManagerImplementation - public Task> AddChainAsync(PrmApeChainAdd args) + public Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - - var service = GetApeManagerService(); - return service.AddChainAsync(args); + return GetApeManagerService().AddChainAsync(args, ctx); } - public Task RemoveChainAsync(PrmApeChainRemove args) + public Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - - var service = GetApeManagerService(); - return service.RemoveChainAsync(args); + return GetApeManagerService().RemoveChainAsync(args, ctx); } - public Task ListChainAsync(PrmApeChainList args) + public Task ListChainAsync(PrmApeChainList args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetApeManagerService(); - return service.ListChainAsync(args); + return GetApeManagerService().ListChainAsync(args, ctx); } #endregion #region ContainerImplementation - public Task GetContainerAsync(PrmContainerGet args) + public Task GetContainerAsync(PrmContainerGet args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetContainerService(); - return service.GetContainerAsync(args); + return GetContainerService().GetContainerAsync(args, ctx); } - public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) + public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx) { - args ??= new PrmContainerGetAll(); - var service = GetContainerService(); - return service.ListContainersAsync(args); + return GetContainerService().ListContainersAsync(args, ctx); } - public Task CreateContainerAsync(PrmContainerCreate args) + public Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetContainerService(); - return service.CreateContainerAsync(args); + return GetContainerService().CreateContainerAsync(args, ctx); } - public Task DeleteContainerAsync(PrmContainerDelete args) + public Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetContainerService(); - return service.DeleteContainerAsync(args); + return GetContainerService().DeleteContainerAsync(args, ctx); } #endregion #region NetworkImplementation - public Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args) + public Task GetNetmapSnapshotAsync(CallContext ctx) { - args ??= new PrmNetmapSnapshot(); - var service = GetNetmapService(); - return service.GetNetmapSnapshotAsync(args); + return GetNetmapService().GetNetmapSnapshotAsync(ctx); } - public Task GetNodeInfoAsync(PrmNodeInfo? args) + public Task GetNodeInfoAsync(CallContext ctx) { - args ??= new PrmNodeInfo(); - var service = GetNetmapService(); - return service.GetLocalNodeInfoAsync(args); + return GetNetmapService().GetLocalNodeInfoAsync(ctx); } - public Task GetNetworkSettingsAsync(PrmNetworkSettings? args) + public Task GetNetworkSettingsAsync(CallContext ctx) { - args ??= new PrmNetworkSettings(); - var service = GetNetmapService(); - return service.GetNetworkSettingsAsync(args.Context!); + return GetNetmapService().GetNetworkSettingsAsync(ctx); } #endregion #region ObjectImplementation - public Task GetObjectHeadAsync(PrmObjectHeadGet args) + public Task GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.GetObjectHeadAsync(args); + return GetObjectService().GetObjectHeadAsync(args, ctx); } - public Task GetObjectAsync(PrmObjectGet args) + public Task GetObjectAsync(PrmObjectGet args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.GetObjectAsync(args); + return GetObjectService().GetObjectAsync(args, ctx); } - public Task GetRangeAsync(PrmRangeGet args) + public Task GetRangeAsync(PrmRangeGet args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.GetRangeAsync(args); + return GetObjectService().GetRangeAsync(args, ctx); } - public Task[]> GetRangeHashAsync(PrmRangeHashGet args) + public Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.GetRangeHashAsync(args); + return GetObjectService().GetRangeHashAsync(args, ctx); } - public Task PutObjectAsync(PrmObjectPut args) + public Task PutObjectAsync(PrmObjectPut args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.PutObjectAsync(args); + return GetObjectService().PutObjectAsync(args, ctx); } - public Task PutSingleObjectAsync(PrmSingleObjectPut args) + public Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.PutSingleObjectAsync(args); + return GetObjectService().PutSingleObjectAsync(args, ctx); } - public Task PatchObjectAsync(PrmObjectPatch args) + public Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx) { - if (args is null) - { - throw new ArgumentNullException(nameof(args)); - } - - var service = GetObjectService(); - return service.PatchObjectAsync(args); + return GetObjectService().PatchObjectAsync(args, ctx); } - public Task DeleteObjectAsync(PrmObjectDelete args) + public Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.DeleteObjectAsync(args); + return GetObjectService().DeleteObjectAsync(args, ctx); } - public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var service = GetObjectService(); - return service.SearchObjectsAsync(args); + return GetObjectService().SearchObjectsAsync(args, ctx); } #endregion #region Session Implementation - public async Task CreateSessionAsync(PrmSessionCreate args) + public async Task CreateSessionAsync(PrmSessionCreate args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - - var token = await CreateSessionInternalAsync(args).ConfigureAwait(false); + var token = await CreateSessionInternalAsync(args, ctx).ConfigureAwait(false); return new FrostFsSessionToken(token); } - internal Task CreateSessionInternalAsync(PrmSessionCreate args) + internal Task CreateSessionInternalAsync(PrmSessionCreate args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - var service = GetSessionService(); - return service.CreateSessionAsync(args); + return service.CreateSessionAsync(args, ctx); } #endregion #region Accounting Implementation - public async Task GetBalanceAsync(PrmBalance? args) + public async Task GetBalanceAsync(CallContext ctx) { - args ??= new PrmBalance(); - - var service = GetAccouningService(); - return await service.GetBallance(args).ConfigureAwait(false); + return await GetAccouningService().GetBallance(ctx).ConfigureAwait(false); } #endregion - #region ToolsImplementation - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) + private async void CheckFrostFsVersionSupport(CallContext ctx) { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - return ObjectTools.CalculateObjectId(header, this.ClientCtx); - } - #endregion - - private async void CheckFrostFsVersionSupport(CallContext? ctx = default) - { - var args = new PrmNodeInfo(ctx); - var service = GetNetmapService(); - var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false); + var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx).ConfigureAwait(false); if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { @@ -559,10 +464,8 @@ public class FrostFSClient : IFrostFSClient public async Task Dial(CallContext ctx) { - var prm = new PrmBalance(ctx); - var service = GetAccouningService(); - _ = await service.GetBallance(prm).ConfigureAwait(false); + _ = await service.GetBallance(ctx).ConfigureAwait(false); return null; } diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 8520528..82df872 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -9,61 +9,57 @@ namespace FrostFS.SDK.Client.Interfaces; public interface IFrostFSClient : IDisposable { #region Network - Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null); + Task GetNetmapSnapshotAsync(CallContext ctx); - Task GetNodeInfoAsync(PrmNodeInfo? args = null); + Task GetNodeInfoAsync(CallContext ctx); - Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null); + Task GetNetworkSettingsAsync(CallContext ctx); #endregion #region Session - Task CreateSessionAsync(PrmSessionCreate args); + Task CreateSessionAsync(PrmSessionCreate args, CallContext ctx); #endregion #region ApeManager - Task> AddChainAsync(PrmApeChainAdd args); + Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx); - Task RemoveChainAsync(PrmApeChainRemove args); + Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx); - Task ListChainAsync(PrmApeChainList args); + Task ListChainAsync(PrmApeChainList args, CallContext ctx); #endregion #region Container - Task GetContainerAsync(PrmContainerGet args); + Task GetContainerAsync(PrmContainerGet args, CallContext ctx); - IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null); + IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx); - Task CreateContainerAsync(PrmContainerCreate args); + Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx); - Task DeleteContainerAsync(PrmContainerDelete args); + Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx); #endregion #region Object - Task GetObjectHeadAsync(PrmObjectHeadGet args); + Task GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx); - Task GetObjectAsync(PrmObjectGet args); + Task GetObjectAsync(PrmObjectGet args, CallContext ctx); - Task GetRangeAsync(PrmRangeGet args); + Task GetRangeAsync(PrmRangeGet args, CallContext ctx); - Task[]> GetRangeHashAsync(PrmRangeHashGet args); + Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx); - Task PutObjectAsync(PrmObjectPut args); + Task PutObjectAsync(PrmObjectPut args, CallContext ctx); - Task PutSingleObjectAsync(PrmSingleObjectPut args); + Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx); - Task PatchObjectAsync(PrmObjectPatch args); + Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx); - Task DeleteObjectAsync(PrmObjectDelete args); + Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx); - IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args); + IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args, CallContext ctx); #endregion #region Account - Task GetBalanceAsync(PrmBalance? args = null); - #endregion - - #region Tools - FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header); + Task GetBalanceAsync(CallContext ctx); #endregion public Task Dial(CallContext ctx); diff --git a/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs index dbdde51..89b0d8a 100644 --- a/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs +++ b/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs @@ -36,13 +36,19 @@ public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatab public override readonly bool Equals(object obj) { - var target = (FrostFsChainTarget)obj; - return Equals(target); + if (obj == null || obj is not FrostFsChainTarget) + return false; + + return Equals((FrostFsChainTarget)obj); + } + public readonly bool Equals(FrostFsChainTarget other) + { + return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal); } public override readonly int GetHashCode() { - return $"{Name}{Type}".GetHashCode(); + return Name.GetHashCode() ^ (int)Type; } public static bool operator ==(FrostFsChainTarget left, FrostFsChainTarget right) @@ -54,9 +60,4 @@ public struct FrostFsChainTarget(FrostFsTargetType type, string name) : IEquatab { return !(left == right); } - - public readonly bool Equals(FrostFsChainTarget other) - { - return Type == other.Type && Name.Equals(other.Name, StringComparison.Ordinal); - } } diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsHeaderResult.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsHeaderResult.cs new file mode 100644 index 0000000..43ea299 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsHeaderResult.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK; + +public class FrostFsHeaderResult +{ + public FrostFsObjectHeader? HeaderInfo { get; internal set; } + + public FrostFsSplitInfo? SplitInfo { get; internal set; } +} diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs index 64e68e2..bd850cb 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs @@ -24,5 +24,4 @@ public class FrostFsSplit(SplitId splitId, public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader; public ReadOnlyCollection? Children { get; } = children; - } diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs new file mode 100644 index 0000000..5030410 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs @@ -0,0 +1,26 @@ +using FrostFS.Object; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK; + +public class FrostFsSplitInfo +{ + private readonly SplitInfo _splitInfo; + + private SplitId? _splitId; + + private FrostFsObjectId? _link; + + private FrostFsObjectId? _lastPart; + + internal FrostFsSplitInfo(SplitInfo splitInfo) + { + _splitInfo = splitInfo; + } + + public SplitId SplitId => _splitId ??= new SplitId(_splitInfo.SplitId.ToUuid()); + + public FrostFsObjectId Link => _link ??= FrostFsObjectId.FromHash(_splitInfo.Link.Value.Span); + + public FrostFsObjectId LastPart => _lastPart ??= FrostFsObjectId.FromHash(_splitInfo.LastPart.Value.Span); +} diff --git a/src/FrostFS.SDK.Client/Parameters/CallContext.cs b/src/FrostFS.SDK.Client/Parameters/CallContext.cs index 05e061c..018b7fc 100644 --- a/src/FrostFS.SDK.Client/Parameters/CallContext.cs +++ b/src/FrostFS.SDK.Client/Parameters/CallContext.cs @@ -3,22 +3,42 @@ using System.Threading; namespace FrostFS.SDK.Client; -public class CallContext() +public readonly struct CallContext(TimeSpan timeout, CancellationToken cancellationToken = default) : IEquatable { + public CancellationToken CancellationToken { get; } = cancellationToken; - // internal Action? PoolErrorHandler { get; set; } + public TimeSpan Timeout { get; } = timeout; - // public FrostFsOwner? OwnerId { get; set; } + internal readonly DateTime? GetDeadline() + { + return Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; + } - // public FrostFsVersion? Version { get; set; } + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not CallContext) + return false; - public CancellationToken CancellationToken { get; set; } + return Equals((CallContext)obj); + } - public TimeSpan Timeout { get; set; } + public bool Equals(CallContext other) + { + return Timeout == other.Timeout && CancellationToken.Equals(other.CancellationToken); + } - public DateTime? Deadline => Timeout.Ticks > 0 ? DateTime.UtcNow.Add(Timeout) : null; + public override int GetHashCode() + { + return CancellationToken.GetHashCode() ^ Timeout.GetHashCode(); + } - // public Action? Callback { get; set; } + public static bool operator ==(CallContext left, CallContext right) + { + return left.Equals(right); + } - // public Collection Interceptors { get; } = []; + public static bool operator !=(CallContext left, CallContext right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/Credentials.cs b/src/FrostFS.SDK.Client/Parameters/Credentials.cs deleted file mode 100644 index 6b712ff..0000000 --- a/src/FrostFS.SDK.Client/Parameters/Credentials.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Security.Cryptography; - -namespace FrostFS.SDK.Client; - -public class Credentials(ECDsa key, FrostFsOwner ownerId) -{ - public ECDsa Key { get; } = key; - - public FrostFsOwner OwnerId { get; } = ownerId; -} diff --git a/src/FrostFS.SDK.Client/Parameters/IContext.cs b/src/FrostFS.SDK.Client/Parameters/IContext.cs deleted file mode 100644 index ebf99a9..0000000 --- a/src/FrostFS.SDK.Client/Parameters/IContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace FrostFS.SDK.Client; - -public interface IContext -{ - /// - /// The method call can be extended with additional behavior like canceling by timeout or user's request, - /// callbacks, interceptors. - /// - /// Additional parameters for calling the method - CallContext? Context { get; } -} diff --git a/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs b/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs index 6d07be0..de1a376 100644 --- a/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs +++ b/src/FrostFS.SDK.Client/Parameters/ISessionToken.cs @@ -8,5 +8,5 @@ public interface ISessionToken /// member. The session has a limited validity period, and applies to a strictly defined set of operations. /// /// Instance of the session obtained from the server - FrostFsSessionToken? SessionToken { get; set; } + FrostFsSessionToken? SessionToken { get; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs new file mode 100644 index 0000000..adcc1b9 --- /dev/null +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs @@ -0,0 +1,43 @@ +namespace FrostFS.SDK.Client; + +public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable +{ + public FrostFsChainTarget Target { get; } = target; + + public FrostFsChain Chain { get; } = chain; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmApeChainAdd) + return false; + + return Equals((PrmApeChainAdd)obj); + } + + public readonly bool Equals(PrmApeChainAdd other) + { + return Target == other.Target + && Chain == other.Chain + && XHeaders == other.XHeaders; + } + + public override readonly int GetHashCode() + { + return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmApeChainAdd left, PrmApeChainAdd right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmApeChainAdd left, PrmApeChainAdd right) + { + return !(left == right); + } +} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs index a9a75bb..a946840 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs @@ -1,6 +1,40 @@ namespace FrostFS.SDK.Client; -public sealed class PrmApeChainList(FrostFsChainTarget target, CallContext? ctx = null) : PrmBase(ctx) +public readonly struct PrmApeChainList(FrostFsChainTarget target, string[]? xheaders = null) : System.IEquatable { public FrostFsChainTarget Target { get; } = target; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmApeChainList) + return false; + + return Equals((PrmApeChainList)obj); + } + + public override readonly int GetHashCode() + { + return Target.GetHashCode() ^ XHeaders.GetHashCode(); + } + + public readonly bool Equals(PrmApeChainList other) + { + return Target == other.Target + && XHeaders == other.XHeaders; + } + + public static bool operator ==(PrmApeChainList left, PrmApeChainList right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmApeChainList left, PrmApeChainList right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs index 9773e6d..7e31ff4 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs @@ -1,8 +1,46 @@ namespace FrostFS.SDK.Client; -public sealed class PrmApeChainRemove(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) +public readonly struct PrmApeChainRemove( + FrostFsChainTarget target, + FrostFsChain chain, + string[]? xheaders = null) : System.IEquatable { public FrostFsChainTarget Target { get; } = target; public FrostFsChain Chain { get; } = chain; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmApeChainRemove) + return false; + + return Equals((PrmApeChainRemove)obj); + } + + public readonly bool Equals(PrmApeChainRemove other) + { + return Target == other.Target + && Chain == other.Chain + && XHeaders == other.XHeaders; + } + + public override readonly int GetHashCode() + { + return Chain.GetHashCode() ^ Target.GetHashCode() ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmApeChainRemove left, PrmApeChainRemove right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmApeChainRemove left, PrmApeChainRemove right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs deleted file mode 100644 index b74901f..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeRemoveAdd.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK.Client; - -public sealed class PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, CallContext? ctx = null) : PrmBase(ctx) -{ - public FrostFsChainTarget Target { get; } = target; - - public FrostFsChain Chain { get; } = chain; -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmBalance.cs b/src/FrostFS.SDK.Client/Parameters/PrmBalance.cs deleted file mode 100644 index 6544287..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmBalance.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace FrostFS.SDK.Client; - -public sealed class PrmBalance(CallContext? ctx = null) : PrmBase(ctx) -{ -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmBase.cs b/src/FrostFS.SDK.Client/Parameters/PrmBase.cs deleted file mode 100644 index 092be46..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmBase.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Specialized; - -namespace FrostFS.SDK.Client; - -public class PrmBase(CallContext? ctx, NameValueCollection? xheaders = null) : IContext -{ - /// - /// FrostFS request X-Headers - /// - public NameValueCollection XHeaders { get; } = xheaders ?? []; - - /// - public CallContext Context { get; } = ctx ?? new CallContext(); -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs index ea29967..d5c6ca0 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerCreate.cs @@ -1,17 +1,61 @@ namespace FrostFS.SDK.Client; -public sealed class PrmContainerCreate(FrostFsContainerInfo container, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmContainerCreate( + FrostFsContainerInfo container, + PrmWait waitParams, + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { - public FrostFsContainerInfo Container { get; set; } = container; + public FrostFsContainerInfo Container { get; } = container; /// /// Since the container becomes available with some delay, it needs to poll the container status /// /// Rules for polling the result - public PrmWait? WaitParams { get; set; } + public PrmWait WaitParams { get; } = waitParams; /// /// Blank session token /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmContainerCreate) + return false; + + return Equals((PrmContainerCreate)obj); + } + + public readonly bool Equals(PrmContainerCreate other) + { + return Container == other.Container + && WaitParams == other.WaitParams + && SessionToken == other.SessionToken + && XHeaders == other.XHeaders; + } + + public override readonly int GetHashCode() + { + return Container.GetHashCode() + ^ WaitParams.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) + ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmContainerCreate left, PrmContainerCreate right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmContainerCreate left, PrmContainerCreate right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs index e6b1d6d..1ad1b33 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerDelete.cs @@ -1,14 +1,52 @@ namespace FrostFS.SDK.Client; -public sealed class PrmContainerDelete(FrostFsContainerId containerId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmContainerDelete( + FrostFsContainerId containerId, + PrmWait waitParams, + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { - public FrostFsContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; } = containerId; /// /// Since the container is removed with some delay, it needs to poll the container status /// /// Rules for polling the result - public PrmWait? WaitParams { get; set; } + public PrmWait WaitParams { get; } = waitParams; - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmContainerDelete) + return false; + + return Equals((PrmContainerDelete)obj); + } + + public readonly bool Equals(PrmContainerDelete other) + { + return ContainerId == other.ContainerId + && WaitParams.Equals(other.WaitParams); + } + + public override int GetHashCode() + { + return ContainerId.GetHashCode() ^ WaitParams.GetHashCode(); + } + + public static bool operator ==(PrmContainerDelete left, PrmContainerDelete right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmContainerDelete left, PrmContainerDelete right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs index 723a4c2..a2e34ae 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerGet.cs @@ -1,6 +1,40 @@ namespace FrostFS.SDK.Client; -public sealed class PrmContainerGet(FrostFsContainerId container, CallContext? ctx = null) : PrmBase(ctx) +public readonly struct PrmContainerGet(FrostFsContainerId container, string[]? xheaders = null) : System.IEquatable { - public FrostFsContainerId Container { get; set; } = container; + public FrostFsContainerId Container { get; } = container; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmContainerGet) + return false; + + return Equals((PrmContainerGet)obj); + } + + public readonly bool Equals(PrmContainerGet other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return Container.GetHashCode() + ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmContainerGet left, PrmContainerGet right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmContainerGet left, PrmContainerGet right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs b/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs index 66e2f9b..50b408e 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmContainerGetAll.cs @@ -1,5 +1,39 @@ -namespace FrostFS.SDK.Client; +using System; -public sealed class PrmContainerGetAll(CallContext? ctx = null) : PrmBase(ctx) +namespace FrostFS.SDK.Client; + +public readonly struct PrmContainerGetAll(string[]? xheaders = null) : IEquatable { + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmContainerGetAll) + return false; + + return Equals((PrmContainerGetAll)obj); + } + + public readonly bool Equals(PrmContainerGetAll other) + { + return XHeaders == other.XHeaders; + } + + public override readonly int GetHashCode() + { + return XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmContainerGetAll left, PrmContainerGetAll right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmContainerGetAll left, PrmContainerGetAll right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs deleted file mode 100644 index 06a379c..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmNetmapSnapshot.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace FrostFS.SDK.Client; - -public sealed class PrmNetmapSnapshot(CallContext? ctx = null) : PrmBase(ctx) -{ -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs b/src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs deleted file mode 100644 index 9731d4d..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmNetworkSettings.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace FrostFS.SDK.Client; - -public sealed class PrmNetworkSettings(CallContext? ctx = null) : PrmBase(ctx) -{ -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs b/src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs deleted file mode 100644 index e93b94f..0000000 --- a/src/FrostFS.SDK.Client/Parameters/PrmNodeInfo.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace FrostFS.SDK.Client; - -public sealed class PrmNodeInfo(CallContext? ctx = null) : PrmBase(ctx) -{ -} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs index 9a42958..fa12755 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectDelete.cs @@ -1,11 +1,51 @@ namespace FrostFS.SDK.Client; -public sealed class PrmObjectDelete(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectDelete( + FrostFsContainerId containerId, + FrostFsObjectId objectId, + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { - public FrostFsContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; } = containerId; - public FrostFsObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; } = objectId; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectDelete) + return false; + + return Equals((PrmObjectDelete)obj); + } + + public readonly bool Equals(PrmObjectDelete other) + { + return ContainerId == other.ContainerId + && ObjectId == other.ObjectId + && SessionToken == other.SessionToken + && XHeaders == other.XHeaders; + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() ^ ObjectId.GetHashCode() ^ (SessionToken != null ? SessionToken.GetHashCode() : 1); + } + + public static bool operator ==(PrmObjectDelete left, PrmObjectDelete right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectDelete left, PrmObjectDelete right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs index a50a657..b5ed7f8 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectGet.cs @@ -1,11 +1,52 @@ namespace FrostFS.SDK.Client; -public sealed class PrmObjectGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectGet( + FrostFsContainerId containerId, + FrostFsObjectId objectId, + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { - public FrostFsContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; } = containerId; - public FrostFsObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; } = objectId; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectGet) + return false; + + return Equals((PrmObjectGet)obj); + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() + ^ ObjectId.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()); + } + + public readonly bool Equals(PrmObjectGet other) + { + return ContainerId == other.ContainerId + && ObjectId == other.ObjectId + && SessionToken == other.SessionToken; + } + + public static bool operator ==(PrmObjectGet left, PrmObjectGet right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectGet left, PrmObjectGet right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs index 84fed94..15e2a25 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectHeadGet.cs @@ -1,11 +1,56 @@ namespace FrostFS.SDK.Client; -public sealed class PrmObjectHeadGet(FrostFsContainerId containerId, FrostFsObjectId objectId, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectHeadGet( + FrostFsContainerId containerId, + FrostFsObjectId objectId, + bool raw = false, + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) + : ISessionToken, System.IEquatable { - public FrostFsContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; } = containerId; - public FrostFsObjectId ObjectId { get; set; } = objectId; + public FrostFsObjectId ObjectId { get; } = objectId; + + public bool Raw { get; } = raw; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectHeadGet) + return false; + + return Equals((PrmObjectHeadGet)obj); + } + + public readonly bool Equals(PrmObjectHeadGet other) + { + return ContainerId == other.ContainerId + && ObjectId == other.ObjectId + && SessionToken == other.SessionToken; + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() + ^ ObjectId.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()); + } + + public static bool operator ==(PrmObjectHeadGet left, PrmObjectHeadGet right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectHeadGet left, PrmObjectHeadGet right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs index 2feb84c..7b26f5b 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectPatch.cs @@ -2,23 +2,70 @@ namespace FrostFS.SDK.Client; -public sealed class PrmObjectPatch(FrostFsAddress address, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectPatch( + FrostFsAddress address, + FrostFsRange range, + Stream? payload, + int maxChunkLength, + FrostFsSessionToken? sessionToken = null, + bool replaceAttributes = false, + FrostFsAttributePair[]? newAttributes = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { public FrostFsAddress Address { get; } = address; - public FrostFsRange Range { get; set; } + public FrostFsRange Range { get; } = range; /// /// A stream with source data /// - public Stream? Payload { get; set; } + public Stream? Payload { get; } = payload; - public FrostFsAttributePair[]? NewAttributes { get; set; } + public FrostFsAttributePair[]? NewAttributes { get; } = newAttributes; - public bool ReplaceAttributes { get; set; } + public bool ReplaceAttributes { get; } = replaceAttributes; - public int MaxPayloadPatchChunkLength { get; set; } + public int MaxChunkLength { get; } = maxChunkLength; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectPatch) + return false; + + return Equals((PrmObjectPatch)obj); + } + + public readonly bool Equals(PrmObjectPatch other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return Address.GetHashCode() + ^ Range.GetHashCode() + ^ (Payload == null ? 0 : Payload.GetHashCode()) + ^ (NewAttributes == null ? 0 : NewAttributes.GetHashCode()) + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) + ^ (ReplaceAttributes ? 1 : 0) + ^ MaxChunkLength; + } + + public static bool operator ==(PrmObjectPatch left, PrmObjectPatch right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectPatch left, PrmObjectPatch right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs index 582d3f5..a2198eb 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs @@ -2,19 +2,26 @@ using System.IO; namespace FrostFS.SDK.Client; -public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectPut( + FrostFsObjectHeader? header, + Stream? payload, + bool clientCut, + int bufferMaxSize = 0, + FrostFsSessionToken? sessionToken = null, + byte[]? customBuffer = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { /// /// Need to provide values like ContainerId and ObjectType to create and object. /// Optional parameters ike Attributes can be provided as well. /// /// Header with required parameters to create an object - public FrostFsObjectHeader? Header { get; set; } + public FrostFsObjectHeader? Header { get; } = header; /// /// A stream with source data /// - public Stream? Payload { get; set; } + public Stream? Payload { get; } = payload; /// /// Object size is limited. In the data exceeds the limit, the object will be splitted. @@ -22,25 +29,60 @@ public sealed class PrmObjectPut(CallContext? ctx = null) : PrmBase(ctx), ISessi /// as a stream and will be cut on server side. /// /// Is client cut is applied - public bool ClientCut { get; set; } + public bool ClientCut { get; } = clientCut; /// /// Overrides default size of the buffer for stream transferring. /// /// Size of the buffer - public int BufferMaxSize { get; set; } + public int BufferMaxSize { get; } = bufferMaxSize; /// /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. /// - public byte[]? CustomBuffer { get; set; } + public byte[]? CustomBuffer { get; } = customBuffer; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; - internal int MaxObjectSizeCache { get; set; } + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; - internal ulong CurrentStreamPosition { get; set; } + internal PutObjectContext PutObjectContext { get; } = new(); - internal ulong FullLength { get; set; } + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectPut) + return false; + + return Equals((PrmObjectPut)obj); + } + + public readonly bool Equals(PrmObjectPut other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return BufferMaxSize + ^ (Header == null ? 0 : Header.GetHashCode()) + ^ (Payload == null ? 0 : Payload.GetHashCode()) + ^ (ClientCut ? 1 : 0) + ^ (CustomBuffer == null ? 0 : CustomBuffer.GetHashCode()) + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) + ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmObjectPut left, PrmObjectPut right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectPut left, PrmObjectPut right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs index 2e6d3d2..508fbd0 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectSearch.cs @@ -1,21 +1,59 @@ -using System.Collections.Generic; +namespace FrostFS.SDK.Client; -namespace FrostFS.SDK.Client; - -public sealed class PrmObjectSearch(FrostFsContainerId containerId, CallContext? ctx = null, params IObjectFilter[] filters) : PrmBase(ctx), ISessionToken +public readonly struct PrmObjectSearch( + FrostFsContainerId containerId, + FrostFsSessionToken? token, + string[]? xheaders = null, + params IObjectFilter[] filters) : ISessionToken, System.IEquatable { /// /// Defines container for the search /// /// - public FrostFsContainerId ContainerId { get; set; } = containerId; + public FrostFsContainerId ContainerId { get; } = containerId; /// /// Defines the search criteria /// /// Collection of filters - public IEnumerable Filters { get; set; } = filters; + public IObjectFilter[] Filters { get; } = filters; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = token; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectSearch) + return false; + + return Equals((PrmObjectSearch)obj); + } + + public readonly bool Equals(PrmObjectSearch other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() + ^ Filters.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) + ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmObjectSearch left, PrmObjectSearch right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectSearch left, PrmObjectSearch right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs index 60286f4..ecb53ed 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmRangeGet.cs @@ -1,11 +1,12 @@ namespace FrostFS.SDK.Client; -public sealed class PrmRangeGet( +public readonly struct PrmRangeGet( FrostFsContainerId containerId, FrostFsObjectId objectId, FrostFsRange range, bool raw = false, - CallContext? ctx = null) : PrmBase(ctx), ISessionToken + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { public FrostFsContainerId ContainerId { get; } = containerId; @@ -16,5 +17,41 @@ public sealed class PrmRangeGet( public bool Raw { get; } = raw; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmRangeGet) + return false; + + return Equals((PrmRangeGet)obj); + } + + public readonly bool Equals(PrmRangeGet other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() + ^ ObjectId.GetHashCode() + ^ Range.GetHashCode() + ^ Raw.GetHashCode(); + } + + public static bool operator ==(PrmRangeGet left, PrmRangeGet right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmRangeGet left, PrmRangeGet right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs b/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs index 95853d7..0d74008 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmRangeHashGet.cs @@ -1,11 +1,12 @@ namespace FrostFS.SDK.Client; -public sealed class PrmRangeHashGet( +public readonly struct PrmRangeHashGet( FrostFsContainerId containerId, FrostFsObjectId objectId, FrostFsRange[] ranges, byte[] salt, - CallContext? ctx = null) : PrmBase(ctx), ISessionToken + FrostFsSessionToken? sessionToken = null, + string[]? xheaders = null) : ISessionToken, System.IEquatable { public FrostFsContainerId ContainerId { get; } = containerId; @@ -16,5 +17,42 @@ public sealed class PrmRangeHashGet( public byte[] Salt { get; } = salt; /// - public FrostFsSessionToken? SessionToken { get; set; } + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmRangeHashGet) + return false; + + return Equals((PrmRangeHashGet)obj); + } + + public readonly bool Equals(PrmRangeHashGet other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return ContainerId.GetHashCode() + ^ ObjectId.GetHashCode() + ^ Ranges.GetHashCode() + ^ Salt.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()); + } + + public static bool operator ==(PrmRangeHashGet left, PrmRangeHashGet right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmRangeHashGet left, PrmRangeHashGet right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs b/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs index 7fdc3de..5a89ebf 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmSessionCreate.cs @@ -1,6 +1,40 @@ namespace FrostFS.SDK.Client; -public sealed class PrmSessionCreate(ulong expiration, CallContext? ctx = null) : PrmBase(ctx) +public readonly struct PrmSessionCreate(ulong expiration, string[]? xheaders = null) : System.IEquatable { - public ulong Expiration { get; set; } = expiration; + public ulong Expiration { get; } = expiration; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmSessionCreate) + return false; + + return Equals((PrmSessionCreate)obj); + } + + public override readonly int GetHashCode() + { + return Expiration.GetHashCode() ^ XHeaders.GetHashCode(); + } + + public readonly bool Equals(PrmSessionCreate other) + { + return Expiration == other.Expiration + && XHeaders == other.XHeaders; + } + + public static bool operator ==(PrmSessionCreate left, PrmSessionCreate right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmSessionCreate left, PrmSessionCreate right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs index 6e5d180..6ca73df 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmSingleObjectPut.cs @@ -1,9 +1,46 @@ namespace FrostFS.SDK.Client; -public sealed class PrmSingleObjectPut(FrostFsObject frostFsObject, CallContext? ctx = null) : PrmBase(ctx), ISessionToken +public struct PrmSingleObjectPut( + FrostFsObject frostFsObject, + string[]? xheaders = null) : ISessionToken, System.IEquatable { public FrostFsObject FrostFsObject { get; set; } = frostFsObject; /// public FrostFsSessionToken? SessionToken { get; set; } + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmSingleObjectPut) + return false; + + return Equals((PrmSingleObjectPut)obj); + } + + public override readonly int GetHashCode() + { + return FrostFsObject.GetHashCode() + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()); + } + + public readonly bool Equals(PrmSingleObjectPut other) + { + return FrostFsObject == other.FrostFsObject + && SessionToken == other.SessionToken; + } + + public static bool operator ==(PrmSingleObjectPut left, PrmSingleObjectPut right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmSingleObjectPut left, PrmSingleObjectPut right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PrmWait.cs b/src/FrostFS.SDK.Client/Parameters/PrmWait.cs index 047f446..2090ef3 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmWait.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmWait.cs @@ -2,7 +2,7 @@ namespace FrostFS.SDK.Client; -public class PrmWait(TimeSpan timeout, TimeSpan pollInterval) +public readonly struct PrmWait(TimeSpan timeout, TimeSpan pollInterval) : IEquatable { private static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(120); private static TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(5); @@ -13,12 +13,40 @@ public class PrmWait(TimeSpan timeout, TimeSpan pollInterval) public static PrmWait DefaultParams { get; } = new PrmWait(DefaultTimeout, DefaultPollInterval); - public TimeSpan Timeout { get; set; } = timeout.Ticks == 0 ? DefaultTimeout : timeout; + public TimeSpan Timeout { get; } = timeout.Ticks == 0 ? DefaultTimeout : timeout; - public TimeSpan PollInterval { get; set; } = pollInterval.Ticks == 0 ? DefaultPollInterval : pollInterval; + public TimeSpan PollInterval { get; } = pollInterval.Ticks == 0 ? DefaultPollInterval : pollInterval; - public DateTime GetDeadline() + public readonly DateTime GetDeadline() { return DateTime.UtcNow.AddTicks(Timeout.Ticks); } + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmWait) + return false; + + return Equals((PrmWait)obj); + } + + public override readonly int GetHashCode() + { + return DefaultTimeout.GetHashCode() ^ DefaultPollInterval.GetHashCode(); + } + + public readonly bool Equals(PrmWait other) + { + return Timeout == other.Timeout && PollInterval == other.PollInterval; + } + + public static bool operator ==(PrmWait left, PrmWait right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmWait left, PrmWait right) + { + return !(left == right); + } } diff --git a/src/FrostFS.SDK.Client/Parameters/PutObjectContext.cs b/src/FrostFS.SDK.Client/Parameters/PutObjectContext.cs new file mode 100644 index 0000000..2976ec5 --- /dev/null +++ b/src/FrostFS.SDK.Client/Parameters/PutObjectContext.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK.Client; + +internal sealed class PutObjectContext +{ + internal int MaxObjectSizeCache { get; set; } + + internal ulong CurrentStreamPosition { get; set; } + + internal ulong FullLength { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs index de984d6..4f8e70c 100644 --- a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs +++ b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs @@ -83,8 +83,7 @@ public class ClientWrapper : ClientStatusMonitor try { - var prmNodeInfo = new PrmNodeInfo(ctx); - var response = await Client!.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); + var response = await Client!.GetNodeInfoAsync(ctx).ConfigureAwait(false); return false; } catch (RpcException) @@ -105,11 +104,7 @@ public class ClientWrapper : ClientStatusMonitor { client = new(WrapperPrm, sessionCache); - var dialCtx = new CallContext - { - Timeout = TimeSpan.FromTicks((long)WrapperPrm.DialTimeout), - CancellationToken = ctx.CancellationToken - }; + var dialCtx = new CallContext(TimeSpan.FromTicks((long)WrapperPrm.DialTimeout), ctx.CancellationToken); var error = await client.Dial(ctx).ConfigureAwait(false); if (!string.IsNullOrEmpty(error)) @@ -132,8 +127,7 @@ public class ClientWrapper : ClientStatusMonitor try { - var prmNodeInfo = new PrmNodeInfo(ctx); - var res = await Client.GetNodeInfoAsync(prmNodeInfo).ConfigureAwait(false); + var res = await Client.GetNodeInfoAsync(ctx).ConfigureAwait(false); } catch (FrostFsException) { diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index acb60b9..7df0d40 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -185,7 +185,7 @@ public partial class Pool : IFrostFSClient InnerPools = inner; - var res = await GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false); + var res = await GetNetworkSettingsAsync(default).ConfigureAwait(false); MaxObjectSize = res.MaxObjectSize; @@ -312,7 +312,7 @@ public partial class Pool : IFrostFSClient private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) { var client = cw.Client; - var networkInfo = await client!.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)).ConfigureAwait(false); + var networkInfo = await client!.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); var epoch = networkInfo.Epoch; @@ -320,9 +320,9 @@ public partial class Pool : IFrostFSClient ? ulong.MaxValue : epoch + duration; - var prmSessionCreate = new PrmSessionCreate(exp, ctx); + var prmSessionCreate = new PrmSessionCreate(exp); - return await client.CreateSessionAsync(prmSessionCreate).ConfigureAwait(false); + return await client.CreateSessionAsync(prmSessionCreate, ctx).ConfigureAwait(false); } internal static string FormCacheKey(string address, string key) @@ -507,139 +507,137 @@ public partial class Pool : IFrostFSClient return statistics; } - public async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot? args = null) + public async Task GetNetmapSnapshotAsync(CallContext ctx) { var client = Connection(); - args ??= new(); - - return await client.Client!.GetNetmapSnapshotAsync(args).ConfigureAwait(false); + return await client.Client!.GetNetmapSnapshotAsync(ctx).ConfigureAwait(false); } - public async Task GetNodeInfoAsync(PrmNodeInfo? args = null) + public async Task GetNodeInfoAsync(CallContext ctx) { var client = Connection(); - return await client.Client!.GetNodeInfoAsync(args).ConfigureAwait(false); + return await client.Client!.GetNodeInfoAsync(ctx).ConfigureAwait(false); } - public async Task GetNetworkSettingsAsync(PrmNetworkSettings? args = null) + public async Task GetNetworkSettingsAsync(CallContext ctx) { var client = Connection(); - return await client.Client!.GetNetworkSettingsAsync(args).ConfigureAwait(false); + return await client.Client!.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); } - public async Task CreateSessionAsync(PrmSessionCreate args) + public async Task CreateSessionAsync(PrmSessionCreate args, CallContext ctx) { var client = Connection(); - return await client.Client!.CreateSessionAsync(args).ConfigureAwait(false); + return await client.Client!.CreateSessionAsync(args, ctx).ConfigureAwait(false); } - public async Task> AddChainAsync(PrmApeChainAdd args) + public async Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) { var client = Connection(); - return await client.Client!.AddChainAsync(args).ConfigureAwait(false); + return await client.Client!.AddChainAsync(args, ctx).ConfigureAwait(false); } - public async Task RemoveChainAsync(PrmApeChainRemove args) + public async Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx) { var client = Connection(); - await client.Client!.RemoveChainAsync(args).ConfigureAwait(false); + await client.Client!.RemoveChainAsync(args, ctx).ConfigureAwait(false); } - public async Task ListChainAsync(PrmApeChainList args) + public async Task ListChainAsync(PrmApeChainList args, CallContext ctx) { var client = Connection(); - return await client.Client!.ListChainAsync(args).ConfigureAwait(false); + return await client.Client!.ListChainAsync(args, ctx).ConfigureAwait(false); } - public async Task GetContainerAsync(PrmContainerGet args) + public async Task GetContainerAsync(PrmContainerGet args, CallContext ctx) { var client = Connection(); - return await client.Client!.GetContainerAsync(args).ConfigureAwait(false); + return await client.Client!.GetContainerAsync(args, ctx).ConfigureAwait(false); } - public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll? args = null) + public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx) { var client = Connection(); - return client.Client!.ListContainersAsync(args); + return client.Client!.ListContainersAsync(args, ctx); } - public async Task CreateContainerAsync(PrmContainerCreate args) + public async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { var client = Connection(); - return await client.Client!.CreateContainerAsync(args).ConfigureAwait(false); + return await client.Client!.CreateContainerAsync(args, ctx).ConfigureAwait(false); } - public async Task DeleteContainerAsync(PrmContainerDelete args) + public async Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) { var client = Connection(); - await client.Client!.DeleteContainerAsync(args).ConfigureAwait(false); + await client.Client!.DeleteContainerAsync(args, ctx).ConfigureAwait(false); } - public async Task GetObjectHeadAsync(PrmObjectHeadGet args) + public async Task GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx) { var client = Connection(); - return await client.Client!.GetObjectHeadAsync(args).ConfigureAwait(false); + return await client.Client!.GetObjectHeadAsync(args, ctx).ConfigureAwait(false); } - public async Task GetObjectAsync(PrmObjectGet args) + public async Task GetObjectAsync(PrmObjectGet args, CallContext ctx) { var client = Connection(); - return await client.Client!.GetObjectAsync(args).ConfigureAwait(false); + return await client.Client!.GetObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task PutObjectAsync(PrmObjectPut args) + public async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) { var client = Connection(); - return await client.Client!.PutObjectAsync(args).ConfigureAwait(false); + return await client.Client!.PutObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task PutSingleObjectAsync(PrmSingleObjectPut args) + public async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { var client = Connection(); - return await client.Client!.PutSingleObjectAsync(args).ConfigureAwait(false); + return await client.Client!.PutSingleObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task PatchObjectAsync(PrmObjectPatch args) + public async Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx) { var client = Connection(); - return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); + return await client.Client!.PatchObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task GetRangeAsync(PrmRangeGet args) + public async Task GetRangeAsync(PrmRangeGet args, CallContext ctx) { var client = Connection(); - return await client.Client!.GetRangeAsync(args).ConfigureAwait(false); + return await client.Client!.GetRangeAsync(args, ctx).ConfigureAwait(false); } - public async Task[]> GetRangeHashAsync(PrmRangeHashGet args) + public async Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx) { var client = Connection(); - return await client.Client!.GetRangeHashAsync(args).ConfigureAwait(false); + return await client.Client!.GetRangeHashAsync(args, ctx).ConfigureAwait(false); } - public async Task PatchAsync(PrmObjectPatch args) + public async Task PatchAsync(PrmObjectPatch args, CallContext ctx) { var client = Connection(); - return await client.Client!.PatchObjectAsync(args).ConfigureAwait(false); + return await client.Client!.PatchObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task DeleteObjectAsync(PrmObjectDelete args) + public async Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx) { var client = Connection(); - await client.Client!.DeleteObjectAsync(args).ConfigureAwait(false); + await client.Client!.DeleteObjectAsync(args, ctx).ConfigureAwait(false); } - public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args, CallContext ctx) { var client = Connection(); - return client.Client!.SearchObjectsAsync(args); + return client.Client!.SearchObjectsAsync(args, ctx); } - public async Task GetBalanceAsync(PrmBalance? args) + public async Task GetBalanceAsync(CallContext ctx) { var client = Connection(); - return await client.Client!.GetBalanceAsync(args).ConfigureAwait(false); + return await client.Client!.GetBalanceAsync(ctx).ConfigureAwait(false); } protected virtual void Dispose(bool disposing) @@ -661,9 +659,4 @@ public partial class Pool : IFrostFSClient Dispose(disposing: true); GC.SuppressFinalize(this); } - - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) - { - throw new NotImplementedException(); - } } diff --git a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs index bdb8e61..e87674f 100644 --- a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs @@ -16,10 +16,8 @@ internal sealed class AccountingServiceProvider : ContextAccessor _accountingServiceClient = accountingServiceClient; } - internal async Task GetBallance(PrmBalance args) + internal async Task GetBallance(CallContext ctx) { - var ctx = args.Context!; - BalanceRequest request = new() { Body = new() @@ -28,10 +26,10 @@ internal sealed class AccountingServiceProvider : ContextAccessor } }; - request.AddMetaHeader(args.XHeaders); + request.AddMetaHeader([]); request.Sign(ClientContext.Key.ECDsaKey); - var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 1c22cdc..0484b16 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -16,10 +16,8 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor _apeManagerServiceClient = apeManagerServiceClient; } - internal async Task> AddChainAsync(PrmApeChainAdd args) + internal async Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) { - var ctx = args.Context!; - AddChainRequest request = new() { Body = new() @@ -32,17 +30,15 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); - var response = await _apeManagerServiceClient!.AddChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _apeManagerServiceClient!.AddChainAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); return response.Body.ChainId.Memory; } - internal async Task RemoveChainAsync(PrmApeChainRemove args) + internal async Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx) { - var ctx = args.Context!; - RemoveChainRequest request = new() { Body = new() @@ -55,15 +51,13 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); - var response = await _apeManagerServiceClient!.RemoveChainAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _apeManagerServiceClient!.RemoveChainAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); } - internal async Task ListChainAsync(PrmApeChainList args) + internal async Task ListChainAsync(PrmApeChainList args, CallContext ctx) { - var ctx = args.Context!; - ListChainsRequest request = new() { Body = new() @@ -75,7 +69,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); - var response = await _apeManagerServiceClient!.ListChainsAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _apeManagerServiceClient!.ListChainsAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index fcdc0e5..6e02f10 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Security.Cryptography; using System.Threading.Tasks; @@ -38,21 +37,19 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService return token; } - internal async Task GetContainerAsync(PrmContainerGet args) + internal async Task GetContainerAsync(PrmContainerGet args, CallContext ctx) { GetRequest request = GetContainerRequest(args.Container.ContainerID, args.XHeaders, ClientContext.Key.ECDsaKey); - var response = await service.GetAsync(request, null, args.Context.Deadline, args.Context.CancellationToken); + var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); return response.Body.Container.ToModel(); } - internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args) + internal async IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx) { - var ctx = args.Context!; - var request = new ListRequest { Body = new() @@ -64,7 +61,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); - var response = await service.ListAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await service.ListAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); @@ -74,10 +71,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService } } - internal async Task CreateContainerAsync(PrmContainerCreate args) + internal async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { - var ctx = args.Context!; - var grpcContainer = args.Container.GetContainer(); grpcContainer.OwnerId ??= ClientContext.Owner.OwnerID; @@ -103,7 +98,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService request.Sign(ClientContext.Key.ECDsaKey); - var response = await service.PutAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await service.PutAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); @@ -112,10 +107,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService return new FrostFsContainerId(response.Body.ContainerId); } - internal async Task DeleteContainerAsync(PrmContainerDelete args) + internal async Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) { - var ctx = args.Context!; - var request = new DeleteRequest { Body = new DeleteRequest.Types.Body @@ -136,7 +129,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService request.Sign(ClientContext.Key.ECDsaKey); - var response = await service.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await service.DeleteAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); @@ -146,7 +139,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Verifier.CheckResponse(response); } - private static GetRequest GetContainerRequest(ContainerID id, NameValueCollection? xHeaders, ECDsa key) + private static GetRequest GetContainerRequest(ContainerID id, string[] xHeaders, ECDsa key) { var request = new GetRequest { @@ -168,13 +161,13 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Removed } - private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait? waitParams, CallContext ctx) + private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait waitParams, CallContext ctx) { - var request = GetContainerRequest(id, null, ClientContext.Key.ECDsaKey); + var request = GetContainerRequest(id, [], ClientContext.Key.ECDsaKey); async Task action() { - var response = await service.GetAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); } @@ -184,9 +177,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService private static async Task WaitFor( Func action, WaitExpects expect, - PrmWait? waitParams) + PrmWait waitParams) { - waitParams ??= PrmWait.DefaultParams; var deadLine = waitParams.GetDeadline(); while (true) diff --git a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs index 570bd24..a1bf17b 100644 --- a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs @@ -42,19 +42,17 @@ internal sealed class NetmapServiceProvider : ContextAccessor return settings; } - internal async Task GetLocalNodeInfoAsync(PrmNodeInfo args) + internal async Task GetLocalNodeInfoAsync(CallContext ctx) { - var ctx = args.Context!; - var request = new LocalNodeInfoRequest { Body = new LocalNodeInfoRequest.Types.Body { } }; - request.AddMetaHeader(args.XHeaders); + request.AddMetaHeader([]); request.Sign(ClientContext.Key.ECDsaKey); - var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); @@ -65,10 +63,10 @@ internal sealed class NetmapServiceProvider : ContextAccessor { var request = new NetworkInfoRequest(); - request.AddMetaHeader(null); + request.AddMetaHeader([]); request.Sign(ClientContext.Key.ECDsaKey); - var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.Deadline, ctx.CancellationToken) + var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken) .ConfigureAwait(false); Verifier.CheckResponse(response); @@ -76,16 +74,14 @@ internal sealed class NetmapServiceProvider : ContextAccessor return response; } - internal async Task GetNetmapSnapshotAsync(PrmNetmapSnapshot args) + internal async Task GetNetmapSnapshotAsync(CallContext ctx) { - var ctx = args.Context!; - var request = new NetmapSnapshotRequest(); - request.AddMetaHeader(args.XHeaders); + request.AddMetaHeader([]); request.Sign(ClientContext.Key.ECDsaKey); - var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index d3c4347..3267ad3 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -43,10 +43,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return token; } - internal async Task GetObjectHeadAsync(PrmObjectHeadGet args) + internal async Task GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx) { - var ctx = args.Context!; - var request = new HeadRequest { Body = new HeadRequest.Types.Body @@ -55,7 +53,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { ContainerId = args.ContainerId.ContainerID, ObjectId = args.ObjectId.ToMessage() - } + }, + Raw = args.Raw } }; @@ -70,17 +69,27 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.Sign(ClientContext.Key.ECDsaKey); - var response = await client!.HeadAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); + var response = await client!.HeadAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken).ConfigureAwait(false); Verifier.CheckResponse(response); - return response.Body.Header.Header.ToModel(); + var result = new FrostFsHeaderResult(); + + if (response.Body.Header != null) + { + result.HeaderInfo = response.Body.Header?.Header.ToModel(); + } + + if (response.Body.SplitInfo != null) + { + result.SplitInfo = new FrostFsSplitInfo(response.Body.SplitInfo); + } + + return result; } - internal async Task GetObjectAsync(PrmObjectGet args) + internal async Task GetObjectAsync(PrmObjectGet args, CallContext ctx) { - var ctx = args.Context!; - var request = new GetRequest { Body = new GetRequest.Types.Body @@ -107,10 +116,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return await GetObject(request, ctx).ConfigureAwait(false); } - internal async Task GetRangeAsync(PrmRangeGet args) + internal async Task GetRangeAsync(PrmRangeGet args, CallContext ctx) { - var ctx = args.Context!; - var request = new GetRangeRequest { Body = new GetRangeRequest.Types.Body @@ -140,14 +147,12 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.Sign(ClientContext.Key.ECDsaKey); - var call = client.GetRange(request, null, ctx.Deadline, ctx.CancellationToken); + var call = client.GetRange(request, null, ctx.GetDeadline(), ctx.CancellationToken); return new RangeReader(call); } - internal async Task[]> GetRangeHashAsync(PrmRangeHashGet args) + internal async Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx) { - var ctx = args.Context!; - var request = new GetRangeHashRequest { Body = new GetRangeHashRequest.Types.Body @@ -182,7 +187,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.Sign(ClientContext.Key.ECDsaKey); - var response = await client.GetRangeHashAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.GetRangeHashAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); @@ -191,11 +196,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return hashCollection; } - - internal async Task DeleteObjectAsync(PrmObjectDelete args) + internal async Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx) { - var ctx = args.Context!; - var request = new DeleteRequest { Body = new DeleteRequest.Types.Body @@ -218,15 +220,13 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); request.Sign(ClientContext.Key.ECDsaKey); - var response = await client.DeleteAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await client.DeleteAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); } - internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args) + internal async IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args, CallContext ctx) { - var ctx = args.Context!; - var request = new SearchRequest { Body = new SearchRequest.Types.Body @@ -265,11 +265,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } } - internal async Task PutObjectAsync(PrmObjectPut args) + internal async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) { - if (args is null) - throw new ArgumentNullException(nameof(args)); - if (args.Header == null) throw new ArgumentNullException(nameof(args), "Header is null"); @@ -278,27 +275,25 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl if (args.ClientCut) { - return await PutClientCutObject(args).ConfigureAwait(false); + return await PutClientCutObject(args, ctx).ConfigureAwait(false); } else { if (args.Header.PayloadLength > 0) - args.FullLength = args.Header.PayloadLength; + args.PutObjectContext.FullLength = args.Header.PayloadLength; else if (args.Payload.CanSeek) - args.FullLength = (ulong)args.Payload.Length; + args.PutObjectContext.FullLength = (ulong)args.Payload.Length; else throw new ArgumentException("The stream does not have a length and payload length is not defined"); - var response = await PutStreamObject(args).ConfigureAwait(false); + var response = await PutStreamObject(args, ctx).ConfigureAwait(false); return response.ObjectId; } } - internal async Task PutSingleObjectAsync(PrmSingleObjectPut args) + internal async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { - var ctx = args.Context!; - var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ClientContext); var request = new PutSingleRequest @@ -317,21 +312,19 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.Sign(ClientContext.Key.ECDsaKey); - var response = await client.PutSingleAsync(request, null, ctx.Deadline, ctx.CancellationToken).ConfigureAwait(false); + var response = await client.PutSingleAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken).ConfigureAwait(false); Verifier.CheckResponse(response); return FrostFsObjectId.FromHash(grpcObject.ObjectId.Value.Span); } - internal async Task PatchObjectAsync(PrmObjectPatch args) + internal async Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx) { - var ctx = args.Context!; - - var chunkSize = args.MaxPayloadPatchChunkLength; + var chunkSize = args.MaxChunkLength; Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); - var call = client.Patch(null, ctx.Deadline, ctx.CancellationToken); + var call = client.Patch(null, ctx.GetDeadline(), ctx.CancellationToken); byte[]? chunkBuffer = null; try @@ -415,36 +408,32 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return response.Body.ObjectId.ToModel(); } - private async Task PutClientCutObject(PrmObjectPut args) + private async Task PutClientCutObject(PrmObjectPut args, CallContext ctx) { - var ctx = args.Context!; - - args.SessionToken ??= await GetDefaultSession(args, ctx).ConfigureAwait(false); - var payloadStream = args.Payload!; var header = args.Header!; if (header.PayloadLength > 0) - args.FullLength = header.PayloadLength; + args.PutObjectContext.FullLength = header.PayloadLength; else if (payloadStream.CanSeek) - args.FullLength = (ulong)payloadStream.Length; + args.PutObjectContext.FullLength = (ulong)payloadStream.Length; else throw new ArgumentException("The stream does not have a length and payload length is not defined"); - if (args.MaxObjectSizeCache == 0) + if (args.PutObjectContext.MaxObjectSizeCache == 0) { - var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(new PrmNetworkSettings(ctx)) + var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx) .ConfigureAwait(false); - args.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; + args.PutObjectContext.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; } - var restBytes = args.FullLength - args.CurrentStreamPosition; - var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.MaxObjectSizeCache, restBytes) : args.MaxObjectSizeCache; + var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition; + var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.PutObjectContext.MaxObjectSizeCache, restBytes) : args.PutObjectContext.MaxObjectSizeCache; //define collection capacity var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; - var objectsCount = args.FullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; + var objectsCount = args.PutObjectContext.FullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; List sentObjectIds = new(objectsCount); @@ -456,13 +445,13 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl args.Header!.Attributes = null; // send all parts except the last one as separate Objects - while (restBytes > (ulong)args.MaxObjectSizeCache) + while (restBytes > (ulong)args.PutObjectContext.MaxObjectSizeCache) { split = new FrostFsSplit(splitId, sentObjectIds.LastOrDefault()); args.Header!.Split = split; - var result = await PutStreamObject(args).ConfigureAwait(false); + var result = await PutStreamObject(args, default).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); @@ -474,18 +463,18 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var largeObjectHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, [.. attributes]) { - PayloadLength = args.FullLength, + PayloadLength = args.PutObjectContext.FullLength, }; args.Header.Split!.ParentHeader = largeObjectHeader; - var result = await PutStreamObject(args).ConfigureAwait(false); + var result = await PutStreamObject(args, default).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds); - _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject, args.Context)).ConfigureAwait(false); + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false); var parentHeader = args.Header.GetHeader(); @@ -495,7 +484,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl // We are here if the payload is placed to one Object. It means no cut action, just simple PUT. args.Header!.Attributes = attributes; - var singlePartResult = await PutStreamObject(args).ConfigureAwait(false); + var singlePartResult = await PutStreamObject(args, default).ConfigureAwait(false); return singlePartResult.ObjectId; } @@ -506,15 +495,13 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl public int ObjectSize = objectSize; } - private async Task PutStreamObject(PrmObjectPut args) + private async Task PutStreamObject(PrmObjectPut args, CallContext ctx) { - var ctx = args.Context!; - var payload = args.Payload!; var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; - var restBytes = args.FullLength - args.CurrentStreamPosition; + var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition; chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize); @@ -524,7 +511,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl try { // 0 means no limit from client, so server side cut is performed - var objectLimitSize = args.ClientCut ? args.MaxObjectSizeCache : 0; + var objectLimitSize = args.ClientCut ? args.PutObjectContext.MaxObjectSizeCache : 0; if (args.CustomBuffer != null) { @@ -634,7 +621,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl throw new ArgumentNullException(nameof(initRequest)); } - var call = client.Put(null, ctx.Deadline, ctx.CancellationToken); + var call = client.Put(null, ctx.GetDeadline(), ctx.CancellationToken); await call.RequestStream.WriteAsync(initRequest).ConfigureAwait(false); @@ -658,7 +645,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl if (initRequest is null) throw new ArgumentNullException(nameof(initRequest)); - var call = client.Get(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Get(initRequest, null, ctx.GetDeadline(), ctx.CancellationToken); return new ObjectReader(call); } @@ -670,7 +657,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl throw new ArgumentNullException(nameof(initRequest)); } - var call = client.Search(initRequest, null, ctx.Deadline, ctx.CancellationToken); + var call = client.Search(initRequest, null, ctx.GetDeadline(), ctx.CancellationToken); return new SearchReader(call); } diff --git a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs index 859d836..e73a3d3 100644 --- a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs @@ -14,10 +14,8 @@ internal sealed class SessionServiceProvider : ContextAccessor _sessionServiceClient = sessionServiceClient; } - internal async Task CreateSessionAsync(PrmSessionCreate args) + internal async Task CreateSessionAsync(PrmSessionCreate args, CallContext ctx) { - var ctx = args.Context!; - var request = new CreateRequest { Body = new CreateRequest.Types.Body @@ -30,12 +28,12 @@ internal sealed class SessionServiceProvider : ContextAccessor request.AddMetaHeader(args.XHeaders); request.Sign(ClientContext.Key.ECDsaKey); - return await CreateSession(request, args.Context!).ConfigureAwait(false); + return await CreateSession(request, ctx).ConfigureAwait(false); } internal async Task CreateSession(CreateRequest request, CallContext ctx) { - var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.Deadline, ctx.CancellationToken); + var response = await _sessionServiceClient!.CreateAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); Verifier.CheckResponse(response); diff --git a/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs index e8ff793..da5a26d 100644 --- a/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs +++ b/src/FrostFS.SDK.Client/Services/Shared/SessionProvider.cs @@ -18,6 +18,6 @@ internal sealed class SessionProvider(ClientContext envCtx) internal async Task GetDefaultSession(ISessionToken args, CallContext ctx) { - return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue, ctx)).ConfigureAwait(false); + return await envCtx.Client.CreateSessionInternalAsync(new PrmSessionCreate(uint.MaxValue), ctx).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index eeb06c0..58f9f30 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -11,16 +11,6 @@ namespace FrostFS.SDK.Client; internal static class ObjectTools { - internal static FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, ClientContext ctx) - { - var grpcHeader = CreateHeader(header, [], ctx); - - if (header.Split != null) - SetSplitValues(grpcHeader, header.Split, ctx); - - return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); - } - internal static Object.Object CreateObject(FrostFsObject @object, ClientContext ctx) { @object.Header.OwnerId ??= ctx.Owner; diff --git a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs index 64aecc6..00a3916 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Specialized; -using System.Linq; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Proto.Interfaces; @@ -11,7 +9,7 @@ namespace FrostFS.SDK.Client; public static class RequestConstructor { public static void AddMetaHeader(this IRequest request, - NameValueCollection? xHeaders, + string[] xHeaders, SessionToken? sessionToken = null) { if (request is null) @@ -25,9 +23,15 @@ public static class RequestConstructor if (sessionToken != null) request.MetaHeader.SessionToken = sessionToken; - if (xHeaders != null && xHeaders.Count > 0) - request.MetaHeader.XHeaders.AddRange( - xHeaders.Cast().SelectMany(key => xHeaders.GetValues(key), - (k, v) => new XHeader { Key = k, Value = v })); + if (xHeaders != null && xHeaders.Length > 0) + { + if (xHeaders.Length % 2 != 0) + throw new ArgumentException("xHeaders with odd length"); + + for (var i = 0; i < xHeaders.Length; i += 2) + { + request.MetaHeader.XHeaders.Add(new XHeader { Key = xHeaders[i], Value = xHeaders[i + 1] }); + } + } } } diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 604fe7c..35804f5 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -16,7 +16,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Tests/GlobalSuppressions.cs b/src/FrostFS.SDK.Tests/GlobalSuppressions.cs deleted file mode 100644 index d1eb84b..0000000 --- a/src/FrostFS.SDK.Tests/GlobalSuppressions.cs +++ /dev/null @@ -1,6 +0,0 @@ -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - - diff --git a/src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs b/src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs new file mode 100644 index 0000000..831f37e --- /dev/null +++ b/src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs @@ -0,0 +1,42 @@ +using System.Security.Cryptography; + +using FrostFS.SDK.Client; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK.Tests.Smoke; + +public abstract class MultiThreadTestsBase +{ + private TestNodeInfo[] nodes; + + protected CallContext? Ctx { get; } + + protected MultiThreadTestsBase() + { + nodes = new TestNodeInfo[4]; + + nodes[0] = new(new Uri(""), ""); + nodes[1] = new(new Uri(""), ""); + nodes[2] = new(new Uri(""), ""); + nodes[3] = new(new Uri(""), ""); + } +} + +public class TestNodeInfo +{ + internal Uri Uri; + + protected ECDsa? Key { get; } + + protected FrostFsOwner? OwnerId { get; } + + protected FrostFsVersion? Version { get; } + + public TestNodeInfo(Uri uri, string keyString) + { + Uri = uri; + Key = keyString.LoadWif(); + OwnerId = FrostFsOwner.FromKey(Key); + Version = new FrostFsVersion(2, 13); + } +} diff --git a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs similarity index 72% rename from src/FrostFS.SDK.Tests/PoolSmokeTests.cs rename to src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs index 0a6f2b3..b58cbb1 100644 --- a/src/FrostFS.SDK.Tests/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs @@ -6,11 +6,11 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.SmokeTests; +namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class PoolSmokeTests : SmokeTestsBase +public class MultithreadPoolSmokeTests : SmokeTestsBase { private static readonly PrmWait lightWait = new(100, 1); @@ -20,7 +20,7 @@ public class PoolSmokeTests : SmokeTestsBase { Key = keyString.LoadWif(), - NodeParams = [new(1, this.url, 100.0f)], + NodeParams = [new(1, url, 100.0f)], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, Logger = null @@ -34,7 +34,7 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)); Assert.Null(error); @@ -59,11 +59,11 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)); Assert.Null(error); - var result = await pool.GetNodeInfoAsync(); + var result = await pool.GetNodeInfoAsync(default); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); @@ -82,8 +82,8 @@ public class PoolSmokeTests : SmokeTestsBase { Key = keyString.LoadWif(), NodeParams = [ - new(1, this.url, 100.0f), - new(2, this.url.Replace('0', '1'), 100.0f) + new(1, url, 100.0f), + new(2, url.Replace('0', '1'), 100.0f) ], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, @@ -93,17 +93,15 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var ctx = new CallContext - { - }; + var ctx = new CallContext(TimeSpan.Zero); var error = await pool.Dial(ctx).ConfigureAwait(true); Assert.Null(error); - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - var result = await client.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(default); var statistics = pool.Statistic(); @@ -121,15 +119,15 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var ctx = new CallContext(); + var ctx = new CallContext(TimeSpan.Zero); var error = await pool.Dial(ctx).ConfigureAwait(true); Assert.Null(error); - using var client = FrostFSClient.GetSingleOwnerInstance(GetSingleOwnerOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - var result = await client.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(default); Assert.False(string.IsNullOrEmpty(callbackText)); Assert.Contains(" took ", callbackText, StringComparison.Ordinal); @@ -142,13 +140,13 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); var prm = new PrmSessionCreate(100); - var token = await pool.CreateSessionAsync(prm).ConfigureAwait(true); + var token = await pool.CreateSessionAsync(prm, default).ConfigureAwait(true); var ownerHash = Base58.Decode(OwnerId!.Value); @@ -164,37 +162,36 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["key1", "value1"]); - createContainerParam.XHeaders.Add("key1", "value1"); - - var containerId = await pool.CreateContainerAsync(createContainerParam); + var containerId = await pool.CreateContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false, - SessionToken = token - }; + bufferMaxSize: 1024, + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); - var objectId = await pool.PutObjectAsync(param).ConfigureAwait(true); + var objectId = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -217,19 +214,17 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - WaitParams = lightWait - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam); + var containerId = await pool.CreateContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; @@ -240,30 +235,28 @@ public class PoolSmokeTests : SmokeTestsBase PayloadLength = 3 }; - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], new FrostFsSplit()), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await pool.PutObjectAsync(param); + var objectId = await pool.PutObjectAsync(param, default); - var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default); - var ecdsaKey = this.keyString.LoadWif(); + var ecdsaKey = keyString.LoadWif(); - var networkInfo = await pool.GetNetmapSnapshotAsync(); + var networkInfo = await pool.GetNetmapSnapshotAsync(default); await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId)); + await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); @@ -286,12 +279,12 @@ public class PoolSmokeTests : SmokeTestsBase { var resultObjectsCount = 0; - PrmObjectSearch searchParam = new(containerId) { Filters = [filter] }; + PrmObjectSearch searchParam = new(containerId, null, [], filter); - await foreach (var objId in pool.SearchObjectsAsync(searchParam)) + await foreach (var objId in pool.SearchObjectsAsync(searchParam, default)) { resultObjectsCount++; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId)); + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); } Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); @@ -307,7 +300,7 @@ public class PoolSmokeTests : SmokeTestsBase var options = GetDefaultParams(); - options.Callback = new((CallStatistics cs) => + options.Callback = new((cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); @@ -315,45 +308,45 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); - var ctx = new CallContext { }; - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); - var createdContainer = await pool.CreateContainerAsync(createContainerParam); + var createdContainer = await pool.CreateContainerAsync(createContainerParam, default); - var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer)); + var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut() - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await pool.PutObjectAsync(param); + var objectId = await pool.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) { hasObject = true; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes!); @@ -363,7 +356,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.True(hasObject); - var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -378,7 +371,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); - await foreach (var _ in pool.ListContainersAsync()) + await foreach (var _ in pool.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -393,52 +386,51 @@ public class PoolSmokeTests : SmokeTestsBase var options = GetDefaultParams(); using var pool = new Pool(options); - options.Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); + options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(pool); - var ctx = new CallContext - { - Timeout = TimeSpan.FromSeconds(20) - }; + var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams); - var container = await pool.CreateContainerAsync(createContainerParam); + var container = await pool.CreateContainerAsync(createContainerParam, ctx); - var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container, ctx)); + var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut(new CallContext()) - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false, - SessionToken = token - }; + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); - var objectId = await pool.PutObjectAsync(param); + var objectId = await pool.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default)) { hasObject = true; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token }); + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); @@ -448,7 +440,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.True(hasObject); - var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token }); + var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -463,7 +455,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); - await foreach (var _ in pool.ListContainersAsync()) + await foreach (var _ in pool.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -482,52 +474,49 @@ public class PoolSmokeTests : SmokeTestsBase using var pool = new Pool(options); - var error = await pool.Dial(new CallContext()).ConfigureAwait(true); + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); Assert.Null(error); await Cleanup(pool); - var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - WaitParams = lightWait - }; + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam); + var containerId = await pool.CreateContainerAsync(createContainerParam, default); - var ctx = new CallContext - { - Timeout = TimeSpan.FromSeconds(10), - }; + var ctx = new CallContext(TimeSpan.FromSeconds(10)); // ctx.Interceptors.Add(new CallbackInterceptor()); - var container = await pool.GetContainerAsync(new PrmContainerGet(containerId, ctx)); + var container = await pool.GetContainerAsync(new PrmContainerGet(containerId), ctx); Assert.NotNull(container); byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = true - }; + payload: new MemoryStream(bytes), + clientCut: true); - var objectId = await pool.PutObjectAsync(param); + var objectId = await pool.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter))) + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); @@ -537,7 +526,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.True(hasObject); - var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -554,7 +543,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); - await foreach (var cid in pool.ListContainersAsync()) + await foreach (var cid in pool.ListContainersAsync(default, default)) { Assert.Fail($"Container {cid.GetValue()} exist"); } @@ -587,9 +576,9 @@ public class PoolSmokeTests : SmokeTestsBase static async Task Cleanup(Pool pool) { - await foreach (var cid in pool.ListContainersAsync()) + await foreach (var cid in pool.ListContainersAsync(default, default)) { - await pool.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }).ConfigureAwait(true); + await pool.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default).ConfigureAwait(true); } } } diff --git a/src/FrostFS.SDK.Tests/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs similarity index 67% rename from src/FrostFS.SDK.Tests/SmokeClientTests.cs rename to src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs index aaf1869..405278c 100644 --- a/src/FrostFS.SDK.Tests/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs @@ -4,23 +4,24 @@ using System.Security.Cryptography; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; +using FrostFS.SDK.SmokeTests; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.SmokeTests; +namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class SmokeClientTests : SmokeTestsBase +public class MultithreadSmokeClientTests : SmokeTestsBase { private static readonly PrmWait lightWait = new(100, 1); [Fact] public async void AccountTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); - var result = await client.GetBalanceAsync(); + var result = await client.GetBalanceAsync(default); Assert.NotNull(result); Assert.True(result.Value == 0); @@ -29,9 +30,9 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NetworkMapTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); - var result = await client.GetNetmapSnapshotAsync(); + var result = await client.GetNetmapSnapshotAsync(default); Assert.True(result.Epoch > 0); Assert.Single(result.NodeInfoCollection); @@ -49,9 +50,9 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); - var result = await client.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(default); Assert.Equal(2, result.Version.Major); Assert.Equal(13, result.Version.Minor); @@ -64,14 +65,14 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoStatisticsTest() { - var options = GetClientOptions(this.keyString, this.url); + var options = GetClientOptions(keyString, url); var callbackContent = string.Empty; options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - using var client = FrostFSClient.GetSingleOwnerInstance(options); + using var client = FrostFSClient.GetInstance(options); - var result = await client.GetNodeInfoAsync(); + var result = await client.GetNodeInfoAsync(default); Assert.NotEmpty(callbackContent); } @@ -79,11 +80,9 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void GetSessionTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); - PrmSessionCreate? prm = new(100); - - var token = await client.CreateSessionAsync(prm); + var token = await client.CreateSessionAsync(new(100), default); var ownerHash = Base58.Decode(OwnerId!.Value); @@ -95,35 +94,33 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void CreateObjectWithSessionToken() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["key1", "value1"]); - createContainerParam.XHeaders.Add("key1", "value1"); - - var containerId = await client.CreateContainerAsync(createContainerParam); + var containerId = await client.CreateContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false, - SessionToken = token - }; + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); - var objectId = await client.PutObjectAsync(param).ConfigureAwait(true); + var objectId = await client.PutObjectAsync(param, default).ConfigureAwait(true); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)) + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) .ConfigureAwait(true); var downloadedBytes = new byte[@object.Header.PayloadLength]; @@ -143,17 +140,15 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void FilterTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - WaitParams = lightWait - }; + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); - var containerId = await client.CreateContainerAsync(createContainerParam); + var containerId = await client.CreateContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; @@ -164,30 +159,28 @@ public class SmokeClientTests : SmokeTestsBase PayloadLength = 3 }; - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], new FrostFsSplit()), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); - var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - var ecdsaKey = this.keyString.LoadWif(); + var ecdsaKey = keyString.LoadWif(); - var networkInfo = await client.GetNetmapSnapshotAsync(); + var networkInfo = await client.GetNetmapSnapshotAsync(default); await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header.Split!.SplitId)); + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); @@ -210,12 +203,12 @@ public class SmokeClientTests : SmokeTestsBase { var resultObjectsCount = 0; - PrmObjectSearch searchParam = new(containerId) { Filters = [filter] }; + PrmObjectSearch searchParam = new(containerId, null, [], filter); - await foreach (var objId in client.SearchObjectsAsync(searchParam)) + await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) { resultObjectsCount++; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId)); + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); } Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); @@ -229,51 +222,53 @@ public class SmokeClientTests : SmokeTestsBase { bool callbackInvoked = false; - var options = GetClientOptions(this.keyString, this.url); + var options = GetClientOptions(keyString, url); - options.Value.Callback = new((CallStatistics cs) => + options.Value.Callback = new((cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); }); - using var client = FrostFSClient.GetSingleOwnerInstance(options); + using var client = FrostFSClient.GetInstance(options); await Cleanup(client); - var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(20) }; + var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")]), ctx); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam); + var createdContainer = await client.CreateContainerAsync(createContainerParam, ctx); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); Assert.True(callbackInvoked); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer) { Filters = [filter] })) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId)); + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); @@ -283,7 +278,7 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -298,7 +293,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - await foreach (var _ in client.ListContainersAsync()) + await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -307,54 +302,53 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void PatchTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam); + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[1024]; for (int i = 0; i < 1024; i++) { - bytes[i] = (byte)31; + bytes[i] = 31; } - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var patch = new byte[16]; for (int i = 0; i < 16; i++) { - patch[i] = (byte)32; + patch[i] = 32; } var range = new FrostFsRange(8, (ulong)patch.Length); - var patchParams = new PrmObjectPatch(new FrostFsAddress(createdContainer, objectId)) - { - Payload = new MemoryStream(patch), - MaxPayloadPatchChunkLength = 32, - Range = range - }; + var patchParams = new PrmObjectPatch( + new FrostFsAddress(createdContainer, objectId), + payload: new MemoryStream(patch), + maxChunkLength: 32, + range: range); - var newIbjId = await client.PatchObjectAsync(patchParams); + var newIbjId = await client.PatchObjectAsync(patchParams, default); - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -378,7 +372,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - await foreach (var _ in client.ListContainersAsync()) + await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -387,16 +381,18 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam); + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[256]; @@ -405,20 +401,16 @@ public class SmokeClientTests : SmokeTestsBase bytes[i] = (byte)i; } - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + var param = new PrmObjectPut( + new FrostFsObjectHeader(containerId: createdContainer, type: FrostFsObjectType.Regular), + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); - var rangeReader = await client.GetRangeAsync(rangeParam); + var rangeReader = await client.GetRangeAsync(rangeParam, default); var downloadedBytes = new byte[rangeParam.Range.Length]; MemoryStream ms = new(downloadedBytes); @@ -433,7 +425,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - await foreach (var _ in client.ListContainersAsync()) + await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -442,16 +434,18 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeHashTest() { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), [new("testKey", "testValue")])); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam); + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer)); + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); var bytes = new byte[256]; @@ -460,20 +454,18 @@ public class SmokeClientTests : SmokeTestsBase bytes[i] = (byte)i; } - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular), - Payload = new MemoryStream(bytes), - ClientCut = false - }; + payload: new MemoryStream(bytes), + clientCut: false); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); - var hashes = await client.GetRangeHashAsync(rangeParam); + var hashes = await client.GetRangeHashAsync(rangeParam, default); foreach (var hash in hashes) { @@ -482,7 +474,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - await foreach (var _ in client.ListContainersAsync()) + await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -494,49 +486,48 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue)); + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(client); - var ctx = new CallContext - { - Timeout = TimeSpan.FromSeconds(20), - - }; + var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), ctx); + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams); - var container = await client.CreateContainerAsync(createContainerParam); + var container = await client.CreateContainerAsync(createContainerParam, ctx); - var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container, ctx)); + var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); var bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = false, - SessionToken = token - }; + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container) { Filters = [filter], SessionToken = token })) + await foreach (var objId in client.SearchObjectsAsync( + new PrmObjectSearch(container, token, [], filter), default)) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId) { SessionToken = token }); + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); @@ -546,7 +537,7 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId) { SessionToken = token }); + var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -561,7 +552,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - await foreach (var _ in client.ListContainersAsync()) + await foreach (var _ in client.ListContainersAsync(default, default)) { Assert.Fail("Containers exist"); } @@ -577,48 +568,48 @@ public class SmokeClientTests : SmokeTestsBase public async void ClientCutScenarioTest(int objectSize) { - var options = GetClientOptions(this.keyString, this.url); + var options = GetClientOptions(keyString, url); options.Value.Interceptors.Add(new CallbackInterceptor()); - using var client = FrostFSClient.GetSingleOwnerInstance(GetClientOptions(this.keyString, this.url)); + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); await Cleanup(client); - var createContainerParam = new PrmContainerCreate(new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))) - { - WaitParams = lightWait - }; + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); - var containerId = await client.CreateContainerAsync(createContainerParam); + var containerId = await client.CreateContainerAsync(createContainerParam, default); - var ctx = new CallContext { Timeout = TimeSpan.FromSeconds(10) }; + var ctx = new CallContext(TimeSpan.FromSeconds(10)); - var container = await client.GetContainerAsync(new PrmContainerGet(containerId, ctx)); + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); Assert.NotNull(container); byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut - { - Header = new FrostFsObjectHeader( + var param = new PrmObjectPut( + new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - Payload = new MemoryStream(bytes), - ClientCut = true - }; + payload: new MemoryStream(bytes), + clientCut: true); - var objectId = await client.PutObjectAsync(param); + var objectId = await client.PutObjectAsync(param, default); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, filter))) + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) { hasObject = true; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId)); + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); Assert.NotNull(objHeader.Attributes); Assert.Single(objHeader.Attributes); @@ -628,7 +619,7 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(hasObject); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId)); + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); @@ -656,7 +647,7 @@ public class SmokeClientTests : SmokeTestsBase break; } - enumerator = client.ListContainersAsync().GetAsyncEnumerator(); + enumerator = client.ListContainersAsync(default, default).GetAsyncEnumerator(); await Task.Delay(500); } while (await enumerator!.MoveNextAsync()); @@ -668,8 +659,8 @@ public class SmokeClientTests : SmokeTestsBase bool callbackInvoked = false; bool intercepterInvoked = false; - var options = GetClientOptions(this.keyString, this.url); - options.Value.Callback = (CallStatistics cs) => + var options = GetClientOptions(keyString, url); + options.Value.Callback = (cs) => { callbackInvoked = true; Assert.True(cs.ElapsedMicroSeconds > 0); @@ -677,9 +668,9 @@ public class SmokeClientTests : SmokeTestsBase options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); - using var client = FrostFSClient.GetSingleOwnerInstance(options); + using var client = FrostFSClient.GetInstance(options); - var result = await client.GetNodeInfoAsync(new PrmNodeInfo()); + var result = await client.GetNodeInfoAsync(default); Assert.True(callbackInvoked); Assert.True(intercepterInvoked); @@ -712,9 +703,9 @@ public class SmokeClientTests : SmokeTestsBase static async Task Cleanup(IFrostFSClient client) { - await foreach (var cid in client.ListContainersAsync()) + await foreach (var cid in client.ListContainersAsync(default, default)) { - await client.DeleteContainerAsync(new PrmContainerDelete(cid) { WaitParams = lightWait }); + await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default); } } } diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs new file mode 100644 index 0000000..feaf838 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs @@ -0,0 +1,585 @@ +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; + +using FrostFS.SDK.Client; +using FrostFS.SDK.Cryptography; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class PoolSmokeTests : SmokeTestsBase +{ + private static readonly PrmWait lightWait = new(100, 1); + + private InitParameters GetDefaultParams() + { + return new InitParameters + { + Key = keyString.LoadWif(), + + NodeParams = [new(1, url, 100.0f)], + ClientBuilder = null, + GracefulCloseOnSwitchTimeout = 30_000_000, + Logger = null + }; + } + + [Fact] + public async void NetworkMapTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)); + + Assert.Null(error); + + var result = await pool.GetNetmapSnapshotAsync(default); + + Assert.True(result.Epoch > 0); + Assert.Single(result.NodeInfoCollection); + + var item = result.NodeInfoCollection[0]; + Assert.Equal(2, item.Version.Major); + Assert.Equal(13, item.Version.Minor); + Assert.Equal(NodeState.Online, item.State); + Assert.True(item.PublicKey.Length > 0); + Assert.Single(item.Addresses); + Assert.Equal(9, item.Attributes.Count); + } + + [Fact] + public async void NodeInfoTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)); + + Assert.Null(error); + + var result = await pool.GetNodeInfoAsync(default); + + 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); + } + + [Fact] + public async void NodeInfoStatisticsTwoNodesTest() + { + var callbackText = string.Empty; + + var options = new InitParameters + { + Key = keyString.LoadWif(), + NodeParams = [ + new(1, url, 100.0f), + new(2, url.Replace('0', '1'), 100.0f) + ], + ClientBuilder = null, + GracefulCloseOnSwitchTimeout = 30_000_000, + Logger = null, + Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" + }; + + using var pool = new Pool(options); + + var ctx = new CallContext(TimeSpan.Zero); + + var error = await pool.Dial(ctx).ConfigureAwait(true); + + Assert.Null(error); + + using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); + + var result = await client.GetNodeInfoAsync(default); + + var statistics = pool.Statistic(); + + Assert.False(string.IsNullOrEmpty(callbackText)); + Assert.Contains(" took ", callbackText, StringComparison.Ordinal); + } + + [Fact] + public async void NodeInfoStatisticsTest() + { + var options = GetDefaultParams(); + + var callbackText = string.Empty; + options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; + + using var pool = new Pool(options); + + var ctx = new CallContext(TimeSpan.Zero); + + var error = await pool.Dial(ctx).ConfigureAwait(true); + + Assert.Null(error); + + using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); + + var result = await client.GetNodeInfoAsync(default); + + Assert.False(string.IsNullOrEmpty(callbackText)); + Assert.Contains(" took ", callbackText, StringComparison.Ordinal); + } + + [Fact] + public async void GetSessionTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + var prm = new PrmSessionCreate(100); + + var token = await pool.CreateSessionAsync(prm, default).ConfigureAwait(true); + + var ownerHash = Base58.Decode(OwnerId!.Value); + + Assert.NotNull(token); + Assert.NotEqual(Guid.Empty, token.Id); + Assert.Equal(33, token.SessionKey.Length); + } + + [Fact] + public async void CreateObjectWithSessionToken() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["key1", "value1"]); + + var containerId = await pool.CreateContainerAsync(createContainerParam, default); + + var bytes = GetRandomBytes(1024); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); + + var objectId = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + } + + [Fact] + public async void FilterTest() + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); + + var containerId = await pool.CreateContainerAsync(createContainerParam, default); + + 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()), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await pool.PutObjectAsync(param, default); + + var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var ecdsaKey = keyString.LoadWif(); + + var networkInfo = await pool.GetNetmapSnapshotAsync(default); + + await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); + + await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); + + await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); + + await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); + + await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); + + await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + + await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); + + await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); + + var checkSum = CheckSum.CreateCheckSum(bytes); + + await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); + + await CheckFilter(pool, containerId, new FilterByPhysicallyStored()); + } + + private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter) + { + var resultObjectsCount = 0; + + PrmObjectSearch searchParam = new(containerId, null, [], filter); + + await foreach (var objId in pool.SearchObjectsAsync(searchParam, default)) + { + resultObjectsCount++; + var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); + } + + Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioTest(int objectSize) + { + bool callbackInvoked = false; + + var options = GetDefaultParams(); + + options.Callback = new((cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var ctx = new CallContext(TimeSpan.Zero); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); + + var createdContainer = await pool.CreateContainerAsync(createContainerParam, ctx); + + var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); + Assert.NotNull(container); + Assert.True(callbackInvoked); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await pool.PutObjectAsync(param, default); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) + { + hasObject = true; + + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes!); + Assert.Equal("fileName", objHeader.Attributes!.First().Key); + Assert.Equal("test", objHeader.Attributes!.First().Value); + } + + Assert.True(hasObject); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + + await foreach (var _ in pool.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioWithSessionTest(int objectSize) + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); + + await Cleanup(pool); + + var ctx = new CallContext(TimeSpan.FromSeconds(20)); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams); + + var container = await pool.CreateContainerAsync(createContainerParam, ctx); + + var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); + Assert.NotNull(containerInfo); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: container, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); + + var objectId = await pool.PutObjectAsync(param, new CallContext(TimeSpan.Zero)); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + var objs = pool.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default); + await foreach (var objId in objs) + { + hasObject = true; + + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(pool); + + await foreach (var _ in pool.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB + [InlineData(64 * 1024 * 1024 - 1)] + [InlineData(64 * 1024 * 1024 + 1)] + [InlineData(2 * 64 * 1024 * 1024 + 256)] + [InlineData(200)] + public async void ClientCutScenarioTest(int objectSize) + { + var options = GetDefaultParams(); + + using var pool = new Pool(options); + + var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + Assert.Null(error); + + await Cleanup(pool); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); + + var containerId = await pool.CreateContainerAsync(createContainerParam, default); + + var ctx = new CallContext(TimeSpan.FromSeconds(10)); + + // ctx.Interceptors.Add(new CallbackInterceptor()); + + var container = await pool.GetContainerAsync(new PrmContainerGet(containerId), ctx); + + Assert.NotNull(container); + + byte[] bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: true); + + var objectId = await pool.PutObjectAsync(param, default); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) + { + hasObject = true; + + var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); 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 @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await CheckFilter(pool, containerId, new FilterByRootObject()); + + await Cleanup(pool); + + await foreach (var cid in pool.ListContainersAsync(default, default)) + { + Assert.Fail($"Container {cid.GetValue()} exist"); + } + } + + private static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + + private static IOptions GetSingleOwnerOptions(string key, string url) + { + return Options.Create(new ClientSettings + { + Key = key, + Host = url + }); + } + + private static IOptions GetOptions(string url) + { + return Options.Create(new ClientSettings + { + Host = url + }); + } + + static async Task Cleanup(Pool pool) + { + await foreach (var cid in pool.ListContainersAsync(default, default)) + { + await pool.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default).ConfigureAwait(true); + } + } +} diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs new file mode 100644 index 0000000..9c3b21f --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs @@ -0,0 +1,709 @@ +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; + +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; +using FrostFS.SDK.Cryptography; +using FrostFS.SDK.SmokeTests; + +using Microsoft.Extensions.Options; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class SmokeClientTests : SmokeTestsBase +{ + private static readonly PrmWait lightWait = new(100, 1); + + [Fact] + public async void AccountTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + var result = await client.GetBalanceAsync(default); + + Assert.NotNull(result); + Assert.True(result.Value == 0); + } + + [Fact] + public async void NodeInfoTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + var result = await client.GetNodeInfoAsync(default); + + 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); + } + + [Fact] + public async void NodeInfoStatisticsTest() + { + var options = GetClientOptions(keyString, url); + + var callbackContent = string.Empty; + options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; + + using var client = FrostFSClient.GetInstance(options); + + var result = await client.GetNodeInfoAsync(default); + + Assert.NotEmpty(callbackContent); + } + + [Fact] + public async void GetSessionTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + var token = await client.CreateSessionAsync(new(100), default); + + var ownerHash = Base58.Decode(OwnerId!.Value); + + Assert.NotNull(token); + Assert.NotEqual(Guid.Empty, token.Id); + Assert.Equal(33, token.SessionKey.Length); + } + + [Fact] + public async void CreateObjectWithSessionToken() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["key1", "value1"]); + + var containerId = await client.CreateContainerAsync(createContainerParam, default); + + var bytes = GetRandomBytes(1024); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); + + var objectId = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) + .ConfigureAwait(true); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(client).ConfigureAwait(true); + } + + [Fact] + public async void FilterTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); + + var containerId = await client.CreateContainerAsync(createContainerParam, default); + + 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()), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await client.PutObjectAsync(param, default); + + var head = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var ecdsaKey = keyString.LoadWif(); + + var networkInfo = await client.GetNetmapSnapshotAsync(default); + + await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); + + await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); + + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); + + await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); + + await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); + + await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + + await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); + + await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); + + var checkSum = CheckSum.CreateCheckSum(bytes); + + await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); + + await CheckFilter(client, containerId, new FilterByPhysicallyStored()); + } + + private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) + { + var resultObjectsCount = 0; + + PrmObjectSearch searchParam = new(containerId, null, [], filter); + + await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) + { + resultObjectsCount++; + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); + } + + Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioTest(int objectSize) + { + bool callbackInvoked = false; + + var options = GetClientOptions(keyString, url); + + options.Value.Callback = new((cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }); + + using var client = FrostFSClient.GetInstance(options); + + await Cleanup(client); + + var ctx = new CallContext(TimeSpan.FromSeconds(20)); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); + + var createdContainer = await client.CreateContainerAsync(createContainerParam, ctx); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); + Assert.NotNull(container); + Assert.True(callbackInvoked); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await client.PutObjectAsync(param, default); + + var filter1 = new FilterByOwnerId(FrostFsMatchType.Equals, OwnerId!); + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, 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 filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) + { + hasObject = true; + + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void PatchTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); + + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); + Assert.NotNull(container); + + var bytes = new byte[1024]; + for (int i = 0; i < 1024; i++) + { + bytes[i] = 31; + } + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await client.PutObjectAsync(param, default); + + var patch = new byte[16]; + for (int i = 0; i < 16; i++) + { + patch[i] = 32; + } + + var range = new FrostFsRange(8, (ulong)patch.Length); + + var patchParams = new PrmObjectPatch( + new FrostFsAddress(createdContainer, objectId), + payload: new MemoryStream(patch), + maxChunkLength: 32, + range: range); + + var newIbjId = await client.PatchObjectAsync(patchParams, default); + + var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + for (int i = 0; i < (int)range.Offset; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + + var rangeEnd = range.Offset + range.Length; + + for (int i = (int)range.Offset; i < (int)rangeEnd; i++) + Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); + + for (int i = (int)rangeEnd; i < bytes.Length; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void RangeTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); + + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); + Assert.NotNull(container); + + var bytes = new byte[256]; + for (int i = 0; i < 256; i++) + { + bytes[i] = (byte)i; + } + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await client.PutObjectAsync(param, default); + + var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); + + var rangeReader = await client.GetRangeAsync(rangeParam, default); + + var downloadedBytes = new byte[rangeParam.Range.Length]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await rangeReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(100, 64)), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Fact] + public async void RangeHashTest() + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams, + xheaders: ["testKey", "testValue"]); + + var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + + var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); + Assert.NotNull(container); + + var bytes = new byte[256]; + for (int i = 0; i < 256; i++) + { + bytes[i] = (byte)i; + } + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: createdContainer, + type: FrostFsObjectType.Regular), + payload: new MemoryStream(bytes), + clientCut: false); + + var objectId = await client.PutObjectAsync(param, default); + + var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); + + var hashes = await client.GetRangeHashAsync(rangeParam, default); + + foreach (var hash in hashes) + { + var x = hash[..32].ToArray(); + } + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB + [InlineData(6 * 1024 * 1024 + 100)] + public async void SimpleScenarioWithSessionTest(int objectSize) + { + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); + + await Cleanup(client); + + var ctx = new CallContext(TimeSpan.FromSeconds(20)); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + PrmWait.DefaultParams); + + var container = await client.CreateContainerAsync(createContainerParam, ctx); + + var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); + Assert.NotNull(containerInfo); + + var bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: container, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: false, + sessionToken: token); + + var objectId = await client.PutObjectAsync(param, default); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default)) + { + hasObject = true; + + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName", objHeader.Attributes.First().Key); + Assert.Equal("test", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await Cleanup(client); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } + + [Theory] + [InlineData(1)] + [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB + [InlineData(64 * 1024 * 1024 - 1)] + [InlineData(64 * 1024 * 1024 + 1)] + [InlineData(2 * 64 * 1024 * 1024 + 256)] + [InlineData(200)] + public async void ClientCutScenarioTest(int objectSize) + { + + var options = GetClientOptions(keyString, url); + options.Value.Interceptors.Add(new CallbackInterceptor()); + + using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + + await Cleanup(client); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + lightWait); + + var containerId = await client.CreateContainerAsync(createContainerParam, default); + + var ctx = new CallContext(TimeSpan.FromSeconds(10)); + + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); + + Assert.NotNull(container); + + byte[] bytes = GetRandomBytes(objectSize); + + var param = new PrmObjectPut( + new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes), + clientCut: true); + + var objectId = await client.PutObjectAsync(param, default); + + // 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); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + + await CheckFilter(client, containerId, new FilterByRootObject()); + + await Cleanup(client); + + var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); + + IAsyncEnumerator? enumerator = null; + do + { + if (deadline <= DateTime.UtcNow) + { + Assert.Fail("Containers exist"); + break; + } + + enumerator = client.ListContainersAsync(default, default).GetAsyncEnumerator(); + await Task.Delay(500); + } + while (await enumerator!.MoveNextAsync()); + } + + [Fact] + public async void NodeInfoCallbackAndInterceptorTest() + { + bool callbackInvoked = false; + bool intercepterInvoked = false; + + var options = GetClientOptions(keyString, url); + options.Value.Callback = (cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }; + + options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); + + using var client = FrostFSClient.GetInstance(options); + + var result = await client.GetNodeInfoAsync(default); + + Assert.True(callbackInvoked); + Assert.True(intercepterInvoked); + + 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); + } + + private static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + + private static IOptions GetClientOptions(string key, string url) + { + return Options.Create(new ClientSettings + { + Key = key, + Host = url + }); + } + + + static async Task Cleanup(IFrostFSClient client) + { + await foreach (var cid in client.ListContainersAsync(default, default)) + { + await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default); + } + } +} diff --git a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs similarity index 94% rename from src/FrostFS.SDK.Tests/SmokeTestsBase.cs rename to src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index b96997c..0b711df 100644 --- a/src/FrostFS.SDK.Tests/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -3,7 +3,7 @@ using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.SmokeTests; +namespace FrostFS.SDK.Tests.Smoke; public abstract class SmokeTestsBase { diff --git a/src/FrostFS.SDK.Tests/ContainerTest.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs similarity index 89% rename from src/FrostFS.SDK.Tests/ContainerTest.cs rename to src/FrostFS.SDK.Tests/Unit/ContainerTest.cs index 0d12107..2088b4b 100644 --- a/src/FrostFS.SDK.Tests/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs @@ -6,7 +6,7 @@ using FrostFS.SDK.Cryptography; using Google.Protobuf; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class ContainerTest : ContainerTestsBase @@ -14,9 +14,9 @@ public class ContainerTest : ContainerTestsBase [Fact] public async void CreateContainerTest() { - var param = new PrmContainerCreate(new FrostFsContainerInfo(Mocker.PlacementPolicy)); + var param = new PrmContainerCreate(new FrostFsContainerInfo(Mocker.PlacementPolicy), PrmWait.DefaultParams); - var result = await GetClient().CreateContainerAsync(param); + var result = await GetClient().CreateContainerAsync(param, default); Assert.NotNull(result); Assert.NotNull(result.GetValue()); @@ -28,7 +28,7 @@ public class ContainerTest : ContainerTestsBase { var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - var result = await GetClient().GetContainerAsync(new PrmContainerGet(cid)); + var result = await GetClient().GetContainerAsync(new PrmContainerGet(cid), default); Assert.NotNull(result); Assert.Equal(Mocker.ContainerGuid, result.Nonce); @@ -43,7 +43,7 @@ public class ContainerTest : ContainerTestsBase Mocker.ContainerIds.Add([0xbb]); Mocker.ContainerIds.Add([0xcc]); - var result = GetClient().ListContainersAsync(); + var result = GetClient().ListContainersAsync(default, default); Assert.NotNull(result); @@ -63,7 +63,7 @@ public class ContainerTest : ContainerTestsBase Mocker.ReturnContainerRemoved = true; var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); - await GetClient().DeleteContainerAsync(new PrmContainerDelete(cid)); + await GetClient().DeleteContainerAsync(new PrmContainerDelete(cid, PrmWait.DefaultParams), default); Assert.Single(Mocker.Requests); diff --git a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs similarity index 78% rename from src/FrostFS.SDK.Tests/ContainerTestsBase.cs rename to src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs index 3a0d396..80a70fe 100644 --- a/src/FrostFS.SDK.Tests/ContainerTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Options; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; public abstract class ContainerTestsBase { @@ -19,7 +19,7 @@ public abstract class ContainerTestsBase Host = "http://localhost:8080" }); - Mocker = new ContainerMocker(this.key) + Mocker = new ContainerMocker(key) { PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), Version = new FrostFsVersion(2, 13), @@ -32,9 +32,9 @@ public abstract class ContainerTestsBase return Client.FrostFSClient.GetTestInstance( Settings, null, - new NetworkMocker(this.key).GetMock().Object, - new SessionMocker(this.key).GetMock().Object, + new NetworkMocker(key).GetMock().Object, + new SessionMocker(key).GetMock().Object, Mocker.GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); + new ObjectMocker(key).GetMock().Object); } } diff --git a/src/FrostFS.SDK.Tests/NetworkTest.cs b/src/FrostFS.SDK.Tests/Unit/NetworkTest.cs similarity index 81% rename from src/FrostFS.SDK.Tests/NetworkTest.cs rename to src/FrostFS.SDK.Tests/Unit/NetworkTest.cs index 9e83bb0..b1146f7 100644 --- a/src/FrostFS.SDK.Tests/NetworkTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/NetworkTest.cs @@ -5,7 +5,7 @@ using FrostFS.SDK.Client; using Google.Protobuf; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class NetworkTest : NetworkTestsBase @@ -28,17 +28,13 @@ public class NetworkTest : NetworkTestsBase Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]); Mocker.Parameters.Add("MaintenanceModeAllowed", [1]); - var param = useContext ? - new PrmNetworkSettings(new CallContext - { - CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20) - }) - : new PrmNetworkSettings(); + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient(DefaultSettings).GetNetworkSettingsAsync(param); + var result = await GetClient(DefaultSettings).GetNetworkSettingsAsync(ctx); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -110,28 +106,13 @@ public class NetworkTest : NetworkTestsBase Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; - PrmNetmapSnapshot param = new(); - - if (useContext) - { - var ctx = new CallContext - { - CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20) - }; - - param = new(ctx); - param.XHeaders.Add("headerKey1", "headerValue1"); - - } - else - { - param = new(); - } + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient(DefaultSettings).GetNetmapSnapshotAsync(param); + var result = await GetClient(DefaultSettings).GetNetmapSnapshotAsync(ctx); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -166,13 +147,10 @@ public class NetworkTest : NetworkTestsBase if (useContext) { Assert.NotNull(Mocker.NetmapSnapshotRequest); - Assert.Single(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); - Assert.Equal(param.XHeaders.Keys[0], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Key); - Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders.First().Value); + Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); Assert.NotNull(Mocker.DateTime); - Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); Assert.True(Mocker.DateTime.Value <= validTimeoutTo); } @@ -206,28 +184,13 @@ public class NetworkTest : NetworkTestsBase Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; - PrmNodeInfo param; - - if (useContext) - { - var ctx = new CallContext - { - CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20) - }; - - param = new(ctx); - param.XHeaders.Add("headerKey1", "headerValue1"); - - } - else - { - param = new(); - } + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient(DefaultSettings).GetNodeInfoAsync(param); + var result = await GetClient(DefaultSettings).GetNodeInfoAsync(ctx); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -246,10 +209,7 @@ public class NetworkTest : NetworkTestsBase Assert.NotNull(Mocker.LocalNodeInfoRequest); if (useContext) { - Assert.Single(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); - Assert.Equal(param.XHeaders.Keys[0], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Key); - Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders.First().Value); - + Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); Assert.NotNull(Mocker.DateTime); diff --git a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs similarity index 79% rename from src/FrostFS.SDK.Tests/NetworkTestsBase.cs rename to src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs index 0cabab5..2bbfb9f 100644 --- a/src/FrostFS.SDK.Tests/NetworkTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs @@ -4,7 +4,7 @@ using FrostFS.SDK.Client.Interfaces; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public abstract class NetworkTestsBase @@ -25,7 +25,7 @@ public abstract class NetworkTestsBase Host = "http://localhost:8080", }; - Mocker = new NetworkMocker(this.key); + Mocker = new NetworkMocker(key); } protected IFrostFSClient GetClient(ClientSettings settings) @@ -34,8 +34,8 @@ public abstract class NetworkTestsBase Options.Create(settings), null, Mocker.GetMock().Object, - new SessionMocker(this.key).GetMock().Object, - new ContainerMocker(this.key).GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); + new SessionMocker(key).GetMock().Object, + new ContainerMocker(key).GetMock().Object, + new ObjectMocker(key).GetMock().Object); } } diff --git a/src/FrostFS.SDK.Tests/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs similarity index 76% rename from src/FrostFS.SDK.Tests/ObjectTest.cs rename to src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 5914605..8d837c5 100644 --- a/src/FrostFS.SDK.Tests/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -9,34 +9,12 @@ using FrostFS.SDK.Client.Mappers.GRPC; using Google.Protobuf; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class ObjectTest : ObjectTestsBase { - [Fact] - public async void GetObjectTest() - { - var client = GetClient(); - - var objectId = client.CalculateObjectId(Mocker.ObjectHeader!); - - var result = await client.GetObjectAsync(new PrmObjectGet(ContainerId, objectId)); - - Assert.NotNull(result); - - Assert.NotNull(Mocker.ObjectHeader); - - Assert.Equal(Mocker.ObjectHeader.ContainerId.GetValue(), result.Header.ContainerId.GetValue()); - Assert.Equal(Mocker.ObjectHeader.OwnerId!.Value, result.Header.OwnerId!.Value); - Assert.Equal(Mocker.ObjectHeader.PayloadLength, result.Header.PayloadLength); - Assert.NotNull(result.Header.Attributes); - Assert.Single(result.Header.Attributes); - Assert.Equal(Mocker.ObjectHeader.Attributes![0].Key, result.Header.Attributes[0].Key); - Assert.Equal(Mocker.ObjectHeader.Attributes![0].Value, result.Header.Attributes[0].Value); - } - [Fact] public async void PutObjectTest() { @@ -46,14 +24,12 @@ public class ObjectTest : ObjectTestsBase var bytes = new byte[1024]; rnd.NextBytes(bytes); - var param = new PrmObjectPut - { - Header = Mocker.ObjectHeader, - Payload = new MemoryStream(bytes), - ClientCut = false - }; + var param = new PrmObjectPut( + Mocker.ObjectHeader, + payload: new MemoryStream(bytes), + clientCut: false); - var result = await GetClient().PutObjectAsync(param); + var result = await GetClient().PutObjectAsync(param, default); var sentMessages = Mocker.ClientStreamWriter!.Messages; @@ -81,13 +57,11 @@ public class ObjectTest : ObjectTestsBase byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); var fileLength = bytes.Length; - var param = new PrmObjectPut - { - Header = Mocker.ObjectHeader, - Payload = new MemoryStream(bytes), - BufferMaxSize = 1024, - ClientCut = true - }; + var param = new PrmObjectPut( + Mocker.ObjectHeader, + payload: new MemoryStream(bytes), + bufferMaxSize: 1024, + clientCut: true); Random rnd = new(); @@ -99,7 +73,7 @@ public class ObjectTest : ObjectTestsBase foreach (var objId in objIds) Mocker.ResultObjectIds!.Add(objId); - var result = await GetClient().PutObjectAsync(param); + var result = await GetClient().PutObjectAsync(param, default); var singleObjects = Mocker.PutSingleRequests.ToArray(); @@ -151,7 +125,7 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(header3.Split.ParentHeader); Assert.NotNull(header3.Split.ParentSignature); Assert.Equal(header2.Split.SplitId, header3.Split.SplitId); - Assert.Equal(bytes[((fileLength / blockSize) * blockSize)..fileLength], payload3); + Assert.Equal(bytes[(fileLength / blockSize * blockSize)..fileLength], payload3); Assert.True(header3.Attributes.Count == 0); //link object @@ -175,7 +149,7 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - await GetClient().DeleteObjectAsync(new PrmObjectDelete(ContainerId, Mocker.ObjectId)); + await GetClient().DeleteObjectAsync(new PrmObjectDelete(ContainerId, Mocker.ObjectId), default); var request = Mocker.DeleteRequests.FirstOrDefault(); Assert.NotNull(request); @@ -188,30 +162,33 @@ public class ObjectTest : ObjectTestsBase { Mocker.ObjectId = new ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Encoding.UTF8.GetBytes("test"))) }.ToModel(); - var response = await GetClient().GetObjectHeadAsync(new PrmObjectHeadGet(ContainerId, Mocker.ObjectId)); + var res = await GetClient().GetObjectHeadAsync(new PrmObjectHeadGet(ContainerId, Mocker.ObjectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); var request = Mocker.HeadRequests.FirstOrDefault(); Assert.NotNull(request); Assert.Equal(ContainerId.ToMessage(), request.Body.Address.ContainerId); Assert.Equal(Mocker.ObjectId.ToMessage(), request.Body.Address.ObjectId); - Assert.NotNull(response); - Assert.Equal(ContainerId.GetValue(), response.ContainerId.GetValue()); + Assert.NotNull(objHeader); + Assert.Equal(ContainerId.GetValue(), objHeader.ContainerId.GetValue()); - Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, response.OwnerId!.Value); - Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), response.Version!.ToString()); + Assert.Equal(Mocker.ObjectHeader!.OwnerId!.Value, objHeader.OwnerId!.Value); + Assert.Equal(Mocker.ObjectHeader!.Version!.ToString(), objHeader.Version!.ToString()); - Assert.Equal(Mocker.HeadResponse!.PayloadLength, response.PayloadLength); + Assert.Equal(Mocker.HeadResponse!.PayloadLength, objHeader.PayloadLength); - Assert.Equal(FrostFsObjectType.Regular, response.ObjectType); + Assert.Equal(FrostFsObjectType.Regular, objHeader.ObjectType); - Assert.NotNull(response.Attributes); - Assert.Single(response.Attributes); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); - Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, response.Attributes.First().Key); - Assert.Equal(Mocker.HeadResponse.Attributes[0].Value, response.Attributes.First().Value); + Assert.Equal(Mocker.HeadResponse.Attributes[0].Key, objHeader.Attributes.First().Key); + Assert.Equal(Mocker.HeadResponse.Attributes[0].Value, objHeader.Attributes.First().Value); - Assert.Null(response.Split); + Assert.Null(objHeader.Split); } [Fact] @@ -229,7 +206,7 @@ public class ObjectTest : ObjectTestsBase var param = new PrmRangeGet(ContainerId, Mocker.ObjectId, new FrostFsRange(100, (ulong)Mocker.RangeResponse.Length)); - var result = await GetClient().GetRangeAsync(param); + var result = await GetClient().GetRangeAsync(param, default); Assert.NotNull(Mocker.GetRangeRequest); @@ -271,7 +248,7 @@ public class ObjectTest : ObjectTestsBase var param = new PrmRangeHashGet(ContainerId, Mocker.ObjectId, [new FrostFsRange(100, len)], salt); - var result = await GetClient().GetRangeHashAsync(param); + var result = await GetClient().GetRangeHashAsync(param, default); Assert.NotNull(Mocker.GetRangeHashRequest); @@ -299,14 +276,13 @@ public class ObjectTest : ObjectTestsBase var range = new FrostFsRange(8, (ulong)patch.Length); - var param = new PrmObjectPatch(address) - { - Payload = new MemoryStream(patch), - MaxPayloadPatchChunkLength = 32, - Range = range - }; + var param = new PrmObjectPatch( + address, + payload: new MemoryStream(patch), + maxChunkLength: 32, + range: range); - var result = await GetClient().PatchObjectAsync(param); + var result = await GetClient().PatchObjectAsync(param, default); Assert.NotNull(result); diff --git a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs similarity index 98% rename from src/FrostFS.SDK.Tests/ObjectTestsBase.cs rename to src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs index 21289e4..ba8bb21 100644 --- a/src/FrostFS.SDK.Tests/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs @@ -4,7 +4,7 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; public abstract class ObjectTestsBase { diff --git a/src/FrostFS.SDK.Tests/SessionTests.cs b/src/FrostFS.SDK.Tests/Unit/SessionTests.cs similarity index 78% rename from src/FrostFS.SDK.Tests/SessionTests.cs rename to src/FrostFS.SDK.Tests/Unit/SessionTests.cs index 9e79b29..b2f1b78 100644 --- a/src/FrostFS.SDK.Tests/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/SessionTests.cs @@ -4,7 +4,7 @@ using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class SessionTest : SessionTestsBase @@ -17,26 +17,21 @@ public class SessionTest : SessionTestsBase var exp = 100u; PrmSessionCreate param; + CallContext ctx; if (useContext) { - var ctx = new CallContext - { - CancellationToken = Mocker.CancellationTokenSource.Token, - Timeout = TimeSpan.FromSeconds(20) - }; - - param = new PrmSessionCreate(exp, ctx); - - param.XHeaders.Add("headerKey1", "headerValue1"); + ctx = new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token); + param = new PrmSessionCreate(exp, ["headerKey1", "headerValue1"]); } else { + ctx = default; param = new PrmSessionCreate(exp); } var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - var result = await GetClient().CreateSessionAsync(param); + var result = await GetClient().CreateSessionAsync(param, ctx); var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); @@ -64,8 +59,8 @@ public class SessionTest : SessionTestsBase if (useContext) { Assert.Single(Mocker.CreateSessionRequest.MetaHeader.XHeaders); - Assert.Equal(param.XHeaders.Keys[0], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Key); - Assert.Equal(param.XHeaders[param.XHeaders.Keys[0]], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Value); + Assert.Equal(param.XHeaders[0], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Key); + Assert.Equal(param.XHeaders[1], Mocker.CreateSessionRequest.MetaHeader.XHeaders.First().Value); Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); Assert.NotNull(Mocker.DateTime); diff --git a/src/FrostFS.SDK.Tests/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs similarity index 81% rename from src/FrostFS.SDK.Tests/SessionTestsBase.cs rename to src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs index bd958fc..4482c2e 100644 --- a/src/FrostFS.SDK.Tests/SessionTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs @@ -5,7 +5,7 @@ using FrostFS.SDK.Cryptography; using Microsoft.Extensions.Options; -namespace FrostFS.SDK.Tests; +namespace FrostFS.SDK.Tests.Unit; public abstract class SessionTestsBase { @@ -28,7 +28,7 @@ public abstract class SessionTestsBase ECDsaKey = key.LoadWif(); OwnerId = FrostFsOwner.FromKey(ECDsaKey); - Mocker = new SessionMocker(this.key) + Mocker = new SessionMocker(key) { PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), Version = new FrostFsVersion(2, 13) @@ -40,9 +40,9 @@ public abstract class SessionTestsBase return Client.FrostFSClient.GetTestInstance( Settings, null, - new NetworkMocker(this.key).GetMock().Object, + new NetworkMocker(key).GetMock().Object, Mocker.GetMock().Object, - new ContainerMocker(this.key).GetMock().Object, - new ObjectMocker(this.key).GetMock().Object); + new ContainerMocker(key).GetMock().Object, + new ObjectMocker(key).GetMock().Object); } } From c9418a1894d6aed0f457ce0925cd8c3462085a61 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 6 Dec 2024 09:01:33 +0300 Subject: [PATCH 33/65] [#28] Client: Use external GRPC Channnel Signed-off-by: Pavel Gross --- .../Extensions/FrostFsExtensions.cs} | 7 +- .../FrostFS.SDK.Client.csproj | 22 ++-- src/FrostFS.SDK.Client/FrostFSClient.cs | 113 ++++++++--------- .../Interfaces/IFrostFSClient.cs | 8 +- .../Interfaces/IObjectWriter.cs | 12 ++ src/FrostFS.SDK.Client/ObjectWriter.cs | 68 ++++++++++ .../Parameters/PrmObjectClientCutPut.cs | 78 ++++++++++++ .../Parameters/PrmObjectPut.cs | 48 ++----- src/FrostFS.SDK.Client/Pool/ClientWrapper.cs | 31 ++--- src/FrostFS.SDK.Client/Pool/InitParameters.cs | 5 +- src/FrostFS.SDK.Client/Pool/Pool.cs | 50 ++++---- src/FrostFS.SDK.Client/Pool/WrapperPrm.cs | 4 +- .../Services/ObjectServiceProvider.cs | 49 +++----- src/FrostFS.SDK.Client/Tools/ClientContext.cs | 20 +-- src/FrostFS.SDK.Cryptography/Extentions.cs | 7 -- .../FrostFS.SDK.Cryptography.csproj | 7 +- .../FrostFS.SDK.Protos.csproj | 8 +- .../Multithread/MultithreadPoolSmokeTests.cs | 75 ++++++----- .../MultithreadSmokeClientTests.cs | 105 ++++++++-------- src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs | 91 ++++++-------- .../Smoke/SmokeClientTests.cs | 117 +++++++++--------- src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 8 ++ .../Unit/ContainerTestsBase.cs | 2 +- .../Unit/NetworkTestsBase.cs | 2 +- src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 17 ++- src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs | 2 +- .../Unit/SessionTestsBase.cs | 2 +- 27 files changed, 520 insertions(+), 438 deletions(-) rename src/{FrostFS.SDK.Cryptography/UUID.cs => FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs} (88%) create mode 100644 src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs create mode 100644 src/FrostFS.SDK.Client/ObjectWriter.cs create mode 100644 src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs diff --git a/src/FrostFS.SDK.Cryptography/UUID.cs b/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs similarity index 88% rename from src/FrostFS.SDK.Cryptography/UUID.cs rename to src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs index 94fba3f..a986a87 100644 --- a/src/FrostFS.SDK.Cryptography/UUID.cs +++ b/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs @@ -4,8 +4,13 @@ using Google.Protobuf; namespace FrostFS.SDK.Cryptography; -public static class UUIDExtension +public static class FrostFsExtensions { + public static ByteString Sha256(this IMessage data) + { + return ByteString.CopyFrom(data.ToByteArray().Sha256()); + } + public static Guid ToUuid(this ByteString id) { if (id == null) diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 7f4df50..2518ce6 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -8,11 +8,15 @@ - true + true - true + true + + + + <_SkipUpgradeNetAnalyzersNuGetWarning>true @@ -20,16 +24,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 4b42fab..eea24fc 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net.Http; using System.Threading.Tasks; using Frostfs.V2.Ape; @@ -12,7 +11,6 @@ using FrostFS.Session; using Grpc.Core; using Grpc.Core.Interceptors; -using Grpc.Net.Client; using Microsoft.Extensions.Options; @@ -27,8 +25,6 @@ namespace FrostFS.SDK.Client; public class FrostFSClient : IFrostFSClient { - private bool isDisposed; - internal ContainerServiceClient? ContainerServiceClient { get; set; } internal ContainerServiceProvider? ContainerServiceProvider { get; set; } @@ -49,9 +45,19 @@ public class FrostFSClient : IFrostFSClient internal ClientContext ClientCtx { get; set; } - public static IFrostFSClient GetInstance(IOptions clientOptions, GrpcChannelOptions? channelOptions = null) + public static IFrostFSClient GetInstance(IOptions clientOptions, Func grpcChannelFactory) { - return new FrostFSClient(clientOptions, channelOptions); + if (clientOptions is null) + { + throw new ArgumentNullException(nameof(clientOptions)); + } + + if (grpcChannelFactory is null) + { + throw new ArgumentNullException(nameof(grpcChannelFactory)); + } + + return new FrostFSClient(clientOptions, grpcChannelFactory); } /// @@ -66,7 +72,7 @@ public class FrostFSClient : IFrostFSClient /// public static IFrostFSClient GetTestInstance( IOptions settings, - GrpcChannelOptions? channelOptions, + Func grpcChannelFactory, NetmapServiceClient netmapService, SessionServiceClient sessionService, ContainerServiceClient containerService, @@ -77,12 +83,38 @@ public class FrostFSClient : IFrostFSClient throw new ArgumentNullException(nameof(settings)); } - return new FrostFSClient(settings, channelOptions, containerService, netmapService, sessionService, objectService); + if (grpcChannelFactory is null) + { + throw new ArgumentNullException(nameof(grpcChannelFactory)); + } + + if (netmapService is null) + { + throw new ArgumentNullException(nameof(netmapService)); + } + + if (sessionService is null) + { + throw new ArgumentNullException(nameof(sessionService)); + } + + if (containerService is null) + { + throw new ArgumentNullException(nameof(containerService)); + } + + if (objectService is null) + { + throw new ArgumentNullException(nameof(objectService)); + } + + return new FrostFSClient( + settings, channel: grpcChannelFactory(settings.Value.Host), containerService, netmapService, sessionService, objectService); } private FrostFSClient( IOptions settings, - GrpcChannelOptions? channelOptions, + ChannelBase channel, ContainerServiceClient containerService, NetmapServiceClient netmapService, SessionServiceClient sessionService, @@ -99,7 +131,7 @@ public class FrostFSClient : IFrostFSClient client: this, key: new ClientKey(ecdsaKey), owner: FrostFsOwner.FromKey(ecdsaKey), - channel: InitGrpcChannel(settings.Value.Host, channelOptions), + channel: channel, version: new FrostFsVersion(2, 13)) { SessionCache = new SessionCache(0), @@ -113,7 +145,7 @@ public class FrostFSClient : IFrostFSClient ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService)); } - private FrostFSClient(IOptions settings, GrpcChannelOptions? channelOptions) + private FrostFSClient(IOptions settings, Func grpcChannelFactory) { var clientSettings = (settings?.Value) ?? throw new ArgumentNullException(nameof(settings), "Options value must be initialized"); @@ -121,13 +153,11 @@ public class FrostFSClient : IFrostFSClient var ecdsaKey = clientSettings.Key.LoadWif(); - var channel = InitGrpcChannel(clientSettings.Host, channelOptions); - ClientCtx = new ClientContext( this, key: new ClientKey(ecdsaKey), owner: FrostFsOwner.FromKey(ecdsaKey), - channel: channel, + channel: grpcChannelFactory(settings.Value.Host), version: new FrostFsVersion(2, 13)) { SessionCache = new SessionCache(0), @@ -145,7 +175,7 @@ public class FrostFSClient : IFrostFSClient client: this, key: new ClientKey(prm.Key), owner: FrostFsOwner.FromKey(prm.Key!), - channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions), + channel: prm.GrpcChannelFactory(prm.Address), version: new FrostFsVersion(2, 13)) { SessionCache = cache, @@ -154,21 +184,6 @@ public class FrostFSClient : IFrostFSClient }; } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && !isDisposed) - { - ClientCtx?.Dispose(); - isDisposed = true; - } - } - #region ApeManagerImplementation public Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) { @@ -246,9 +261,14 @@ public class FrostFSClient : IFrostFSClient return GetObjectService().GetRangeHashAsync(args, ctx); } - public Task PutObjectAsync(PrmObjectPut args, CallContext ctx) + public Task PutObjectAsync(PrmObjectPut args, CallContext ctx) { - return GetObjectService().PutObjectAsync(args, ctx); + return GetObjectService().PutStreamObjectAsync(args, ctx); + } + + public Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) + { + return GetObjectService().PutClientCutObjectAsync(args, ctx); } public Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) @@ -308,9 +328,6 @@ public class FrostFSClient : IFrostFSClient private CallInvoker? CreateInvoker() { - if (isDisposed) - throw new FrostFsInvalidObjectException("Client is disposed."); - CallInvoker? callInvoker = null; if (ClientCtx.Interceptors != null) @@ -441,27 +458,6 @@ public class FrostFSClient : IFrostFSClient return ObjectServiceProvider; } - - private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions) - { - try - { - var uri = new Uri(host); - - if (channelOptions != null) - return GrpcChannel.ForAddress(uri, channelOptions); - - return GrpcChannel.ForAddress(uri, new GrpcChannelOptions - { - HttpHandler = new HttpClientHandler() - }); - } - catch (UriFormatException e) - { - throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}"); - } - } - public async Task Dial(CallContext ctx) { var service = GetAccouningService(); @@ -474,9 +470,4 @@ public class FrostFSClient : IFrostFSClient { throw new NotImplementedException(); } - - public void Close() - { - Dispose(); - } } diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 82df872..cce7364 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -6,7 +6,7 @@ using Frostfs.V2.Ape; namespace FrostFS.SDK.Client.Interfaces; -public interface IFrostFSClient : IDisposable +public interface IFrostFSClient { #region Network Task GetNetmapSnapshotAsync(CallContext ctx); @@ -47,7 +47,9 @@ public interface IFrostFSClient : IDisposable Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx); - Task PutObjectAsync(PrmObjectPut args, CallContext ctx); + Task PutObjectAsync(PrmObjectPut args, CallContext ctx); + + Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx); Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx); @@ -63,6 +65,4 @@ public interface IFrostFSClient : IDisposable #endregion public Task Dial(CallContext ctx); - - public void Close(); } diff --git a/src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs b/src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs new file mode 100644 index 0000000..2d1abda --- /dev/null +++ b/src/FrostFS.SDK.Client/Interfaces/IObjectWriter.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace FrostFS.SDK.Client.Interfaces +{ + public interface IObjectWriter : IDisposable + { + Task WriteAsync(ReadOnlyMemory memory); + + Task CompleteAsync(); + } +} diff --git a/src/FrostFS.SDK.Client/ObjectWriter.cs b/src/FrostFS.SDK.Client/ObjectWriter.cs new file mode 100644 index 0000000..4fad9c9 --- /dev/null +++ b/src/FrostFS.SDK.Client/ObjectWriter.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; + +using FrostFS.Object; +using FrostFS.SDK.Client.Interfaces; + +using Google.Protobuf; + +namespace FrostFS.SDK.Client +{ + internal sealed class ObjectWriter : IObjectWriter + { + private readonly ClientContext ctx; + private readonly PrmObjectPutBase args; + private readonly ObjectStreamer streamer; + private bool disposedValue; + + internal ObjectWriter(ClientContext ctx, PrmObjectPutBase args, ObjectStreamer streamer) + { + this.ctx = ctx; + this.args = args; + this.streamer = streamer; + } + + public async Task WriteAsync(ReadOnlyMemory memory) + { + var chunkRequest = new PutRequest + { + Body = new PutRequest.Types.Body + { + Chunk = UnsafeByteOperations.UnsafeWrap(memory) + } + }; + + chunkRequest.Sign(this.ctx.Key.ECDsaKey); + + await streamer.Write(chunkRequest).ConfigureAwait(false); + } + + public async Task CompleteAsync() + { + var response = await streamer.Close().ConfigureAwait(false); + + Verifier.CheckResponse(response); + + return FrostFsObjectId.FromHash(response.Body.ObjectId.Value.Span); + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + streamer.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs new file mode 100644 index 0000000..706e367 --- /dev/null +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs @@ -0,0 +1,78 @@ +using System.IO; + +namespace FrostFS.SDK.Client; + +public readonly struct PrmObjectClientCutPut( + FrostFsObjectHeader? header, + Stream? payload, + int bufferMaxSize = 0, + FrostFsSessionToken? sessionToken = null, + byte[]? customBuffer = null, + string[]? xheaders = null) : PrmObjectPutBase, System.IEquatable +{ + /// + /// Need to provide values like ContainerId and ObjectType to create and object. + /// Optional parameters ike Attributes can be provided as well. + /// + /// Header with required parameters to create an object + public FrostFsObjectHeader? Header { get; } = header; + + /// + /// A stream with source data + /// + public Stream? Payload { get; } = payload; + + /// + /// Overrides default size of the buffer for stream transferring. + /// + /// Size of the buffer + public int BufferMaxSize { get; } = bufferMaxSize; + + /// + /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. + /// + public byte[]? CustomBuffer { get; } = customBuffer; + + /// + public FrostFsSessionToken? SessionToken { get; } = sessionToken; + + /// + /// FrostFS request X-Headers + /// + public string[] XHeaders { get; } = xheaders ?? []; + + internal PutObjectContext PutObjectContext { get; } = new(); + + public override readonly bool Equals(object obj) + { + if (obj == null || obj is not PrmObjectClientCutPut) + return false; + + return Equals((PrmObjectClientCutPut)obj); + } + + public readonly bool Equals(PrmObjectClientCutPut other) + { + return GetHashCode() == other.GetHashCode(); + } + + public override readonly int GetHashCode() + { + return BufferMaxSize + ^ (Header == null ? 0 : Header.GetHashCode()) + ^ (Payload == null ? 0 : Payload.GetHashCode()) + ^ (CustomBuffer == null ? 0 : CustomBuffer.GetHashCode()) + ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) + ^ XHeaders.GetHashCode(); + } + + public static bool operator ==(PrmObjectClientCutPut left, PrmObjectClientCutPut right) + { + return left.Equals(right); + } + + public static bool operator !=(PrmObjectClientCutPut left, PrmObjectClientCutPut right) + { + return !(left == right); + } +} diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs index a2198eb..78eaf1e 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectPut.cs @@ -1,15 +1,17 @@ -using System.IO; - namespace FrostFS.SDK.Client; +internal interface PrmObjectPutBase : ISessionToken +{ + FrostFsObjectHeader? Header { get; } + + string[] XHeaders { get; } +} + + public readonly struct PrmObjectPut( FrostFsObjectHeader? header, - Stream? payload, - bool clientCut, - int bufferMaxSize = 0, FrostFsSessionToken? sessionToken = null, - byte[]? customBuffer = null, - string[]? xheaders = null) : ISessionToken, System.IEquatable + string[]? xheaders = null) : PrmObjectPutBase, System.IEquatable { /// /// Need to provide values like ContainerId and ObjectType to create and object. @@ -18,30 +20,6 @@ public readonly struct PrmObjectPut( /// Header with required parameters to create an object public FrostFsObjectHeader? Header { get; } = header; - /// - /// A stream with source data - /// - public Stream? Payload { get; } = payload; - - /// - /// Object size is limited. In the data exceeds the limit, the object will be splitted. - /// If the parameter is true, the client side cut is applied. Otherwise, the data is transferred - /// as a stream and will be cut on server side. - /// - /// Is client cut is applied - public bool ClientCut { get; } = clientCut; - - /// - /// Overrides default size of the buffer for stream transferring. - /// - /// Size of the buffer - public int BufferMaxSize { get; } = bufferMaxSize; - - /// - /// Allows to define a buffer for chunks to manage by the memory allocation and releasing. - /// - public byte[]? CustomBuffer { get; } = customBuffer; - /// public FrostFsSessionToken? SessionToken { get; } = sessionToken; @@ -67,11 +45,7 @@ public readonly struct PrmObjectPut( public override readonly int GetHashCode() { - return BufferMaxSize - ^ (Header == null ? 0 : Header.GetHashCode()) - ^ (Payload == null ? 0 : Payload.GetHashCode()) - ^ (ClientCut ? 1 : 0) - ^ (CustomBuffer == null ? 0 : CustomBuffer.GetHashCode()) + return (Header == null ? 0 : Header.GetHashCode()) ^ (SessionToken == null ? 0 : SessionToken.GetHashCode()) ^ XHeaders.GetHashCode(); } @@ -85,4 +59,4 @@ public readonly struct PrmObjectPut( { return !(left == right); } -} +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs index 4f8e70c..552ee2a 100644 --- a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs +++ b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs @@ -71,8 +71,6 @@ public class ClientWrapper : ClientStatusMonitor return; await Task.Delay((int)WrapperPrm.GracefulCloseOnSwitchTimeout).ConfigureAwait(false); - - Client.Close(); } // restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy. @@ -98,31 +96,18 @@ public class ClientWrapper : ClientStatusMonitor await ScheduleGracefulClose().ConfigureAwait(false); } - FrostFSClient? client = null; + FrostFSClient? client = new(WrapperPrm, sessionCache); - try + var error = await client.Dial(ctx).ConfigureAwait(false); + if (!string.IsNullOrEmpty(error)) { - client = new(WrapperPrm, sessionCache); - - var dialCtx = new CallContext(TimeSpan.FromTicks((long)WrapperPrm.DialTimeout), ctx.CancellationToken); - - var error = await client.Dial(ctx).ConfigureAwait(false); - if (!string.IsNullOrEmpty(error)) - { - SetUnhealthyOnDial(); - return wasHealthy; - } - - lock (_lock) - { - Client = client; - } - - client = null; + SetUnhealthyOnDial(); + return wasHealthy; } - finally + + lock (_lock) { - client?.Dispose(); + Client = client; } try diff --git a/src/FrostFS.SDK.Client/Pool/InitParameters.cs b/src/FrostFS.SDK.Client/Pool/InitParameters.cs index 3f4d14e..66da23f 100644 --- a/src/FrostFS.SDK.Client/Pool/InitParameters.cs +++ b/src/FrostFS.SDK.Client/Pool/InitParameters.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using System.Security.Cryptography; +using Grpc.Core; using Grpc.Core.Interceptors; using Grpc.Net.Client; @@ -10,7 +11,7 @@ using Microsoft.Extensions.Logging; namespace FrostFS.SDK.Client; // InitParameters contains values used to initialize connection Pool. -public class InitParameters +public class InitParameters(Func grpcChannelFactory) { public ECDsa? Key { get; set; } @@ -39,4 +40,6 @@ public class InitParameters public Action? Callback { get; set; } public Collection Interceptors { get; } = []; + + public Func GrpcChannelFactory { get; set; } = grpcChannelFactory; } diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index 7df0d40..b4bb357 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -134,20 +134,20 @@ public partial class Pool : IFrostFSClient int i = 0; foreach (var nodeParams in RebalanceParams.NodesParams) { - var clients = new ClientWrapper[nodeParams.Weights.Count]; + var wrappers = new ClientWrapper[nodeParams.Weights.Count]; for (int j = 0; j < nodeParams.Addresses.Count; j++) { - ClientWrapper? client = null; + ClientWrapper? wrapper = null; bool dialed = false; try { - client = clients[j] = ClientBuilder(nodeParams.Addresses[j]); + wrapper = wrappers[j] = ClientBuilder(nodeParams.Addresses[j]); - await client.Dial(ctx).ConfigureAwait(false); + await wrapper.Dial(ctx).ConfigureAwait(false); dialed = true; - var token = await InitSessionForDuration(ctx, client, RebalanceParams.SessionExpirationDuration, Key.ECDsaKey, false) + var token = await InitSessionForDuration(ctx, wrapper, RebalanceParams.SessionExpirationDuration, Key.ECDsaKey, false) .ConfigureAwait(false); var key = FormCacheKey(nodeParams.Addresses[j], Key.PublicKey); @@ -158,13 +158,13 @@ public partial class Pool : IFrostFSClient catch (RpcException ex) { if (!dialed) - client!.SetUnhealthyOnDial(); + wrapper!.SetUnhealthyOnDial(); else - client!.SetUnhealthy(); + wrapper!.SetUnhealthy(); if (logger != null) { - FrostFsMessages.SessionCreationError(logger, client!.WrapperPrm.Address, ex.Message); + FrostFsMessages.SessionCreationError(logger, wrapper!.WrapperPrm.Address, ex.Message); } } catch (FrostFsInvalidObjectException) @@ -175,7 +175,7 @@ public partial class Pool : IFrostFSClient var sampler = new Sampler(nodeParams.Weights.ToArray()); - inner[i] = new InnerPool(sampler, clients); + inner[i] = new InnerPool(sampler, wrappers); i++; } @@ -277,7 +277,7 @@ public partial class Pool : IFrostFSClient parameters.ClientBuilder ??= new Func((address) => { - var wrapperPrm = new WrapperPrm + var wrapperPrm = new WrapperPrm() { Address = address, Key = parameters.Key!, @@ -287,7 +287,9 @@ public partial class Pool : IFrostFSClient ErrorThreshold = parameters.ErrorThreshold, GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout, Callback = parameters.Callback, - Interceptors = parameters.Interceptors + Interceptors = parameters.Interceptors, + GrpcChannelFactory = parameters.GrpcChannelFactory + }; return new ClientWrapper(wrapperPrm, pool); @@ -334,14 +336,14 @@ public partial class Pool : IFrostFSClient { CancellationTokenSource.Cancel(); - if (InnerPools != null) - { - // close all clients - foreach (var innerPool in InnerPools) - foreach (var client in innerPool.Clients) - if (client.IsDialed()) - client.Client?.Close(); - } + //if (InnerPools != null) + //{ + // // close all clients + // foreach (var innerPool in InnerPools) + // foreach (var client in innerPool.Clients) + // if (client.IsDialed()) + // client.Client?.Close(); + //} } // startRebalance runs loop to monitor connection healthy status. @@ -586,12 +588,18 @@ public partial class Pool : IFrostFSClient return await client.Client!.GetObjectAsync(args, ctx).ConfigureAwait(false); } - public async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) + public async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) { var client = Connection(); return await client.Client!.PutObjectAsync(args, ctx).ConfigureAwait(false); } + public async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) + { + var client = Connection(); + return await client.Client!.PutClientCutObjectAsync(args, ctx).ConfigureAwait(false); + } + public async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { var client = Connection(); @@ -655,8 +663,6 @@ public partial class Pool : IFrostFSClient public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); - GC.SuppressFinalize(this); } } diff --git a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs index b68b6ee..83ac869 100644 --- a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs +++ b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs @@ -2,8 +2,8 @@ using System.Collections.ObjectModel; using System.Security.Cryptography; +using Grpc.Core; using Grpc.Core.Interceptors; -using Grpc.Net.Client; using Microsoft.Extensions.Logging; @@ -28,7 +28,7 @@ public struct WrapperPrm internal Action PoolRequestInfoCallback { get; set; } - internal GrpcChannelOptions GrpcChannelOptions { get; set; } + internal Func GrpcChannelFactory { get; set; } internal ulong GracefulCloseOnSwitchTimeout { get; set; } diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 3267ad3..8561b50 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.Session; @@ -265,33 +266,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } } - internal async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) - { - if (args.Header == null) - throw new ArgumentNullException(nameof(args), "Header is null"); - - if (args.Payload == null) - throw new ArgumentNullException(nameof(args), "Payload is null"); - - if (args.ClientCut) - { - return await PutClientCutObject(args, ctx).ConfigureAwait(false); - } - else - { - if (args.Header.PayloadLength > 0) - args.PutObjectContext.FullLength = args.Header.PayloadLength; - else if (args.Payload.CanSeek) - args.PutObjectContext.FullLength = (ulong)args.Payload.Length; - else - throw new ArgumentException("The stream does not have a length and payload length is not defined"); - - var response = await PutStreamObject(args, ctx).ConfigureAwait(false); - - return response.ObjectId; - } - } - internal async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ClientContext); @@ -408,7 +382,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return response.Body.ObjectId.ToModel(); } - private async Task PutClientCutObject(PrmObjectPut args, CallContext ctx) + internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { var payloadStream = args.Payload!; var header = args.Header!; @@ -451,7 +425,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl args.Header!.Split = split; - var result = await PutStreamObject(args, default).ConfigureAwait(false); + var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); @@ -468,7 +442,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl args.Header.Split!.ParentHeader = largeObjectHeader; - var result = await PutStreamObject(args, default).ConfigureAwait(false); + var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); sentObjectIds.Add(result.ObjectId); @@ -484,7 +458,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl // We are here if the payload is placed to one Object. It means no cut action, just simple PUT. args.Header!.Attributes = attributes; - var singlePartResult = await PutStreamObject(args, default).ConfigureAwait(false); + var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); return singlePartResult.ObjectId; } @@ -495,7 +469,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl public int ObjectSize = objectSize; } - private async Task PutStreamObject(PrmObjectPut args, CallContext ctx) + private async Task PutMultipartStreamObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { var payload = args.Payload!; @@ -511,7 +485,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl try { // 0 means no limit from client, so server side cut is performed - var objectLimitSize = args.ClientCut ? args.PutObjectContext.MaxObjectSizeCache : 0; + var objectLimitSize = args.PutObjectContext.MaxObjectSizeCache; if (args.CustomBuffer != null) { @@ -573,7 +547,14 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } } - private async Task> GetUploadStream(PrmObjectPut args, CallContext ctx) + internal async Task PutStreamObjectAsync(PrmObjectPutBase args, CallContext ctx) + { + var stream = await GetUploadStream(args, ctx).ConfigureAwait(false); + + return new ObjectWriter(ClientContext, args, stream); + } + + private async Task> GetUploadStream(PrmObjectPutBase args, CallContext ctx) { var header = args.Header!; diff --git a/src/FrostFS.SDK.Client/Tools/ClientContext.cs b/src/FrostFS.SDK.Client/Tools/ClientContext.cs index 133ad3f..e2522c9 100644 --- a/src/FrostFS.SDK.Client/Tools/ClientContext.cs +++ b/src/FrostFS.SDK.Client/Tools/ClientContext.cs @@ -1,12 +1,12 @@ using System; using System.Collections.ObjectModel; +using Grpc.Core; using Grpc.Core.Interceptors; -using Grpc.Net.Client; namespace FrostFS.SDK.Client; -public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner owner, GrpcChannel channel, FrostFsVersion version) : IDisposable +public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner owner, ChannelBase channel, FrostFsVersion version) { private string? sessionKey; @@ -14,7 +14,7 @@ public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner own internal string? Address { get; } = channel.Target; - internal GrpcChannel Channel { get; private set; } = channel; + internal ChannelBase Channel { get; private set; } = channel; internal FrostFsVersion Version { get; } = version; @@ -45,18 +45,4 @@ public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner own return sessionKey; } } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - Channel?.Dispose(); - } - } } diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index 9d61972..a3f051f 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,8 +1,6 @@ using System.Security.Cryptography; using System.Threading; -using Google.Protobuf; - using Org.BouncyCastle.Crypto.Digests; namespace FrostFS.SDK.Cryptography; @@ -25,11 +23,6 @@ public static class Extentions return hash; } - public static ByteString Sha256(this IMessage data) - { - return ByteString.CopyFrom(data.ToByteArray().Sha256()); - } - public static byte[] Sha256(this byte[] value) { bool lockTaken = false; diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 35804f5..53c9003 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -9,6 +9,10 @@ true + + + <_SkipUpgradeNetAnalyzersNuGetWarning>true + true @@ -16,8 +20,7 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 8f69fe0..8e7250a 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -9,15 +9,19 @@ true + + + <_SkipUpgradeNetAnalyzersNuGetWarning>true + true - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs index b58cbb1..0cd3d6b 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs @@ -16,10 +16,9 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase private InitParameters GetDefaultParams() { - return new InitParameters + return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), - NodeParams = [new(1, url, 100.0f)], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, @@ -32,7 +31,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); @@ -57,7 +56,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); @@ -78,7 +77,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var callbackText = string.Empty; - var options = new InitParameters + var options = new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), NodeParams = [ @@ -91,7 +90,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" }; - using var pool = new Pool(options); + var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); @@ -99,9 +98,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase Assert.Null(error); - using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - - var result = await client.GetNodeInfoAsync(default); + var result = await pool.GetNodeInfoAsync(default); var statistics = pool.Statistic(); @@ -117,7 +114,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase var callbackText = string.Empty; options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - using var pool = new Pool(options); + var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); @@ -125,9 +122,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase Assert.Null(error); - using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - - var result = await client.GetNodeInfoAsync(default); + var result = await pool.GetNodeInfoAsync(default); Assert.False(string.IsNullOrEmpty(callbackText)); Assert.Contains(" took ", callbackText, StringComparison.Ordinal); @@ -138,7 +133,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -160,7 +155,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -184,19 +179,19 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - bufferMaxSize: 1024, - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); var downloadedBytes = new byte[@object.Header.PayloadLength]; MemoryStream ms = new(downloadedBytes); - ReadOnlyMemory? chunk = null; + ReadOnlyMemory? chunk; while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); @@ -212,7 +207,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -240,11 +235,12 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], - new FrostFsSplit()), - payload: new MemoryStream(bytes), - clientCut: false); + new FrostFsSplit())); - var objectId = await pool.PutObjectAsync(param, default); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default); @@ -306,7 +302,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase Assert.True(cs.ElapsedMicroSeconds > 0); }); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -331,11 +327,12 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await pool.PutObjectAsync(param, default); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -385,7 +382,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -414,11 +411,12 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await pool.PutObjectAsync(param, default); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -472,7 +470,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -496,15 +494,14 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut( + var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: true); + payload: new MemoryStream(bytes)); - var objectId = await pool.PutObjectAsync(param, default); + var objectId = await pool.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs index 405278c..e72e657 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs @@ -19,7 +19,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void AccountTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetBalanceAsync(default); @@ -30,7 +30,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void NetworkMapTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNetmapSnapshotAsync(default); @@ -50,7 +50,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNodeInfoAsync(default); @@ -65,12 +65,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoStatisticsTest() { - var options = GetClientOptions(keyString, url); + var options = ClientOptions; var callbackContent = string.Empty; options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); @@ -80,12 +80,10 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void GetSessionTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var token = await client.CreateSessionAsync(new(100), default); - var ownerHash = Base58.Decode(OwnerId!.Value); - Assert.NotNull(token); Assert.NotEqual(Guid.Empty, token.Id); Assert.Equal(33, token.SessionKey.Length); @@ -94,7 +92,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void CreateObjectWithSessionToken() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -114,11 +112,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await client.PutObjectAsync(param, default).ConfigureAwait(true); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) .ConfigureAwait(true); @@ -140,7 +139,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void FilterTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -164,11 +163,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], - new FrostFsSplit()), - payload: new MemoryStream(bytes), - clientCut: false); + new FrostFsSplit())); - var objectId = await client.PutObjectAsync(param, default); + 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); @@ -222,7 +222,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase { bool callbackInvoked = false; - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Callback = new((cs) => { @@ -230,7 +230,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase Assert.True(cs.ElapsedMicroSeconds > 0); }); - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); @@ -253,11 +253,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -302,7 +303,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void PatchTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -326,11 +327,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var patch = new byte[16]; for (int i = 0; i < 16; i++) @@ -381,7 +383,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void RangeTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -402,11 +404,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase } var param = new PrmObjectPut( - new FrostFsObjectHeader(containerId: createdContainer, type: FrostFsObjectType.Regular), - payload: new MemoryStream(bytes), - clientCut: false); + new FrostFsObjectHeader(containerId: createdContainer, type: FrostFsObjectType.Regular)); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); @@ -434,7 +437,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [Fact] public async void RangeHashTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -457,11 +460,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, - type: FrostFsObjectType.Regular), - payload: new MemoryStream(bytes), - clientCut: false); + type: FrostFsObjectType.Regular)); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); @@ -486,7 +490,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); @@ -510,11 +514,12 @@ public class MultithreadSmokeClientTests : SmokeTestsBase containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -567,11 +572,10 @@ public class MultithreadSmokeClientTests : SmokeTestsBase [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { - - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Interceptors.Add(new CallbackInterceptor()); - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); @@ -589,15 +593,14 @@ public class MultithreadSmokeClientTests : SmokeTestsBase byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut( + var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: true); + payload: new MemoryStream(bytes)); - var objectId = await client.PutObjectAsync(param, default); + var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -659,7 +662,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase bool callbackInvoked = false; bool intercepterInvoked = false; - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Callback = (cs) => { callbackInvoked = true; @@ -668,7 +671,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs index feaf838..ce626d5 100644 --- a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs @@ -4,8 +4,6 @@ using System.Security.Cryptography; using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] @@ -16,10 +14,9 @@ public class PoolSmokeTests : SmokeTestsBase private InitParameters GetDefaultParams() { - return new InitParameters + return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), - NodeParams = [new(1, url, 100.0f)], ClientBuilder = null, GracefulCloseOnSwitchTimeout = 30_000_000, @@ -32,7 +29,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); @@ -57,7 +54,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)); @@ -78,7 +75,7 @@ public class PoolSmokeTests : SmokeTestsBase { var callbackText = string.Empty; - var options = new InitParameters + var options = new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) { Key = keyString.LoadWif(), NodeParams = [ @@ -91,7 +88,7 @@ public class PoolSmokeTests : SmokeTestsBase Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" }; - using var pool = new Pool(options); + var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); @@ -99,9 +96,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.Null(error); - using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - - var result = await client.GetNodeInfoAsync(default); + var result = await pool.GetNodeInfoAsync(default); var statistics = pool.Statistic(); @@ -117,7 +112,7 @@ public class PoolSmokeTests : SmokeTestsBase var callbackText = string.Empty; options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - using var pool = new Pool(options); + var pool = new Pool(options); var ctx = new CallContext(TimeSpan.Zero); @@ -125,9 +120,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.Null(error); - using var client = FrostFSClient.GetInstance(GetSingleOwnerOptions(keyString, url)); - - var result = await client.GetNodeInfoAsync(default); + var result = await pool.GetNodeInfoAsync(default); Assert.False(string.IsNullOrEmpty(callbackText)); Assert.Contains(" took ", callbackText, StringComparison.Ordinal); @@ -138,7 +131,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -160,7 +153,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -184,11 +177,12 @@ public class PoolSmokeTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); @@ -211,7 +205,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -239,11 +233,12 @@ public class PoolSmokeTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], - new FrostFsSplit()), - payload: new MemoryStream(bytes), - clientCut: false); + new FrostFsSplit())); - var objectId = await pool.PutObjectAsync(param, default); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); @@ -305,7 +300,7 @@ public class PoolSmokeTests : SmokeTestsBase Assert.True(cs.ElapsedMicroSeconds > 0); }); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -332,11 +327,12 @@ public class PoolSmokeTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await pool.PutObjectAsync(param, default); + var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -386,7 +382,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -415,11 +411,12 @@ public class PoolSmokeTests : SmokeTestsBase containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await pool.PutObjectAsync(param, new CallContext(TimeSpan.Zero)); + var stream = await pool.PutObjectAsync(param, new CallContext(TimeSpan.Zero)).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -474,7 +471,7 @@ public class PoolSmokeTests : SmokeTestsBase { var options = GetDefaultParams(); - using var pool = new Pool(options); + var pool = new Pool(options); var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); @@ -498,15 +495,14 @@ public class PoolSmokeTests : SmokeTestsBase byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut( + var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: true); + payload: new MemoryStream(bytes)); - var objectId = await pool.PutObjectAsync(param, default); + var objectId = await pool.PutClientCutObjectAsync(param, default).ConfigureAwait(true); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -558,23 +554,6 @@ public class PoolSmokeTests : SmokeTestsBase return bytes; } - private static IOptions GetSingleOwnerOptions(string key, string url) - { - return Options.Create(new ClientSettings - { - Key = key, - Host = url - }); - } - - private static IOptions GetOptions(string url) - { - return Options.Create(new ClientSettings - { - Host = url - }); - } - static async Task Cleanup(Pool pool) { await foreach (var cid in pool.ListContainersAsync(default, default)) diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs index 9c3b21f..db43eef 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs @@ -19,7 +19,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void AccountTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetBalanceAsync(default); @@ -30,7 +30,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetNodeInfoAsync(default); @@ -45,12 +45,12 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void NodeInfoStatisticsTest() { - var options = GetClientOptions(keyString, url); + var options = ClientOptions; var callbackContent = string.Empty; options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); @@ -60,7 +60,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void GetSessionTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var token = await client.CreateSessionAsync(new(100), default); @@ -74,7 +74,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void CreateObjectWithSessionToken() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -94,11 +94,12 @@ public class SmokeClientTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await client.PutObjectAsync(param, default).ConfigureAwait(true); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) .ConfigureAwait(true); @@ -120,7 +121,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void FilterTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -144,11 +145,12 @@ public class SmokeClientTests : SmokeTestsBase containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")], - new FrostFsSplit()), - payload: new MemoryStream(bytes), - clientCut: false); + new FrostFsSplit())); - var objectId = await client.PutObjectAsync(param, default); + 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); @@ -202,7 +204,7 @@ public class SmokeClientTests : SmokeTestsBase { bool callbackInvoked = false; - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Callback = new((cs) => { @@ -210,7 +212,7 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(cs.ElapsedMicroSeconds > 0); }); - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); @@ -233,11 +235,12 @@ public class SmokeClientTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + 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)) @@ -292,7 +295,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void PatchTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -316,24 +319,25 @@ public class SmokeClientTests : SmokeTestsBase new FrostFsObjectHeader( containerId: createdContainer, type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false); + [new FrostFsAttributePair("fileName", "test")])); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - var patch = new byte[16]; - for (int i = 0; i < 16; i++) + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); + + var patch = new byte[256]; + for (int i = 0; i < patch.Length; i++) { patch[i] = 32; } - var range = new FrostFsRange(8, (ulong)patch.Length); + var range = new FrostFsRange(64, (ulong)patch.Length); var patchParams = new PrmObjectPatch( new FrostFsAddress(createdContainer, objectId), payload: new MemoryStream(patch), - maxChunkLength: 32, + maxChunkLength: 256, range: range); var newIbjId = await client.PatchObjectAsync(patchParams, default); @@ -371,7 +375,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -394,26 +398,27 @@ public class SmokeClientTests : SmokeTestsBase var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, - type: FrostFsObjectType.Regular), - payload: new MemoryStream(bytes), - clientCut: false); + type: FrostFsObjectType.Regular)); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); + + var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(50, 100)); var rangeReader = await client.GetRangeAsync(rangeParam, default); var downloadedBytes = new byte[rangeParam.Range.Length]; MemoryStream ms = new(downloadedBytes); - ReadOnlyMemory? chunk = null; + ReadOnlyMemory? chunk; while ((chunk = await rangeReader!.ReadChunk()) != null) { ms.Write(chunk.Value.Span); } - Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(100, 64)), SHA256.HashData(downloadedBytes)); + Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(50, 100)), SHA256.HashData(downloadedBytes)); await Cleanup(client); @@ -426,7 +431,7 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void RangeHashTest() { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); @@ -449,11 +454,12 @@ public class SmokeClientTests : SmokeTestsBase var param = new PrmObjectPut( new FrostFsObjectHeader( containerId: createdContainer, - type: FrostFsObjectType.Regular), - payload: new MemoryStream(bytes), - clientCut: false); + type: FrostFsObjectType.Regular)); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); @@ -478,7 +484,7 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(6 * 1024 * 1024 + 100)] public async void SimpleScenarioWithSessionTest(int objectSize) { - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); @@ -502,11 +508,12 @@ public class SmokeClientTests : SmokeTestsBase containerId: container, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: false, sessionToken: token); - var objectId = await client.PutObjectAsync(param, default); + var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var objectId = await stream.CompleteAsync(); var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -558,11 +565,10 @@ public class SmokeClientTests : SmokeTestsBase [InlineData(200)] public async void ClientCutScenarioTest(int objectSize) { - - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Interceptors.Add(new CallbackInterceptor()); - using var client = FrostFSClient.GetInstance(GetClientOptions(keyString, url)); + var client = FrostFSClient.GetInstance(options, GrpcChannel); await Cleanup(client); @@ -580,15 +586,14 @@ public class SmokeClientTests : SmokeTestsBase byte[] bytes = GetRandomBytes(objectSize); - var param = new PrmObjectPut( + var param = new PrmObjectClientCutPut( new FrostFsObjectHeader( containerId: containerId, type: FrostFsObjectType.Regular, [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes), - clientCut: true); + payload: new MemoryStream(bytes)); - var objectId = await client.PutObjectAsync(param, default); + var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); // var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); @@ -607,14 +612,12 @@ public class SmokeClientTests : SmokeTestsBase //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); @@ -657,7 +660,7 @@ public class SmokeClientTests : SmokeTestsBase bool callbackInvoked = false; bool intercepterInvoked = false; - var options = GetClientOptions(keyString, url); + var options = ClientOptions; options.Value.Callback = (cs) => { callbackInvoked = true; @@ -666,7 +669,7 @@ public class SmokeClientTests : SmokeTestsBase options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); - using var client = FrostFSClient.GetInstance(options); + var client = FrostFSClient.GetInstance(options, GrpcChannel); var result = await client.GetNodeInfoAsync(default); diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index 0b711df..5a9942d 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -3,6 +3,10 @@ using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; +using Grpc.Core; + +using Microsoft.Extensions.Options; + namespace FrostFS.SDK.Tests.Smoke; public abstract class SmokeTestsBase @@ -25,4 +29,8 @@ public abstract class SmokeTestsBase OwnerId = FrostFsOwner.FromKey(Key); Version = new FrostFsVersion(2, 13); } + + protected static Func GrpcChannel => (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)); + + protected IOptions ClientOptions => Options.Create(new ClientSettings { Key = keyString, Host = url }); } diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs index 80a70fe..b6cd61c 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs @@ -31,7 +31,7 @@ public abstract class ContainerTestsBase { return Client.FrostFSClient.GetTestInstance( Settings, - null, + (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)), new NetworkMocker(key).GetMock().Object, new SessionMocker(key).GetMock().Object, Mocker.GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs index 2bbfb9f..9452220 100644 --- a/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/NetworkTestsBase.cs @@ -32,7 +32,7 @@ public abstract class NetworkTestsBase { return Client.FrostFSClient.GetTestInstance( Options.Create(settings), - null, + (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)), Mocker.GetMock().Object, new SessionMocker(key).GetMock().Object, new ContainerMocker(key).GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 8d837c5..445dd80 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -24,12 +24,12 @@ public class ObjectTest : ObjectTestsBase var bytes = new byte[1024]; rnd.NextBytes(bytes); - var param = new PrmObjectPut( - Mocker.ObjectHeader, - payload: new MemoryStream(bytes), - clientCut: false); + var param = new PrmObjectPut(Mocker.ObjectHeader); - var result = await GetClient().PutObjectAsync(param, default); + var stream = await GetClient().PutObjectAsync(param, default).ConfigureAwait(true); + + await stream.WriteAsync(bytes.AsMemory()); + var result = await stream.CompleteAsync(); var sentMessages = Mocker.ClientStreamWriter!.Messages; @@ -57,11 +57,10 @@ public class ObjectTest : ObjectTestsBase byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); var fileLength = bytes.Length; - var param = new PrmObjectPut( + var param = new PrmObjectClientCutPut( Mocker.ObjectHeader, payload: new MemoryStream(bytes), - bufferMaxSize: 1024, - clientCut: true); + bufferMaxSize: 1024); Random rnd = new(); @@ -73,7 +72,7 @@ public class ObjectTest : ObjectTestsBase foreach (var objId in objIds) Mocker.ResultObjectIds!.Add(objId); - var result = await GetClient().PutObjectAsync(param, default); + var result = await GetClient().PutClientCutObjectAsync(param, default); var singleObjects = Mocker.PutSingleRequests.ToArray(); diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs index ba8bb21..cff9064 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs @@ -50,7 +50,7 @@ public abstract class ObjectTestsBase { return FrostFSClient.GetTestInstance( Settings, - null, + (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)), NetworkMocker.GetMock().Object, SessionMocker.GetMock().Object, ContainerMocker.GetMock().Object, diff --git a/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs index 4482c2e..3706a91 100644 --- a/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs @@ -39,7 +39,7 @@ public abstract class SessionTestsBase { return Client.FrostFSClient.GetTestInstance( Settings, - null, + (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)), new NetworkMocker(key).GetMock().Object, Mocker.GetMock().Object, new ContainerMocker(key).GetMock().Object, From abd9b5d0d0aa6ae23451ba1c00ff10ee8a7e2ff1 Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Tue, 10 Dec 2024 18:04:22 +0300 Subject: [PATCH 34/65] [#39] Add CODEOWNERS Signed-off-by: Vitaliy Potyarkin --- CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..03bb5e4 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +.* @PavelGrossSpb +.forgejo/.* @potyarkin +Makefile @potyarkin From 543247e4d9dad9dc111f0003605cdf4540821d37 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 9 Dec 2024 23:54:50 +0300 Subject: [PATCH 35/65] [#28] Client: add method to calculate ObjectId Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFSClient.cs | 8 ++++ .../Interfaces/IFrostFSClient.cs | 2 + .../Models/Object/FrostFsSplitInfo.cs | 6 ++- src/FrostFS.SDK.Client/Pool/Pool.cs | 12 ++++++ .../Services/ObjectServiceProvider.cs | 2 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 38 ++++++++++++++----- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index eea24fc..9135f27 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -290,6 +290,14 @@ public class FrostFSClient : IFrostFSClient { return GetObjectService().SearchObjectsAsync(args, ctx); } + + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) + { + if (header == null) + throw new ArgumentNullException(nameof(header)); + + return ObjectTools.CalculateObjectId(header, ClientCtx.Owner, ClientCtx.Version, ClientCtx.Key); + } #endregion #region Session Implementation diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index cce7364..160e4e0 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -65,4 +65,6 @@ public interface IFrostFSClient #endregion public Task Dial(CallContext ctx); + + FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header); } diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs index 5030410..d8f8783 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplitInfo.cs @@ -20,7 +20,9 @@ public class FrostFsSplitInfo public SplitId SplitId => _splitId ??= new SplitId(_splitInfo.SplitId.ToUuid()); - public FrostFsObjectId Link => _link ??= FrostFsObjectId.FromHash(_splitInfo.Link.Value.Span); + public FrostFsObjectId? Link => _link ??= _splitInfo.Link == null + ? null : FrostFsObjectId.FromHash(_splitInfo.Link.Value.Span); - public FrostFsObjectId LastPart => _lastPart ??= FrostFsObjectId.FromHash(_splitInfo.LastPart.Value.Span); + public FrostFsObjectId? LastPart => _lastPart ??= _splitInfo.LastPart == null + ? null : FrostFsObjectId.FromHash(_splitInfo.LastPart.Value.Span); } diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index b4bb357..56ac3bf 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -39,6 +39,8 @@ public partial class Pool : IFrostFSClient private OwnerID? _ownerId; + private FrostFsVersion _version; + private FrostFsOwner? _owner; private FrostFsOwner Owner @@ -94,6 +96,8 @@ public partial class Pool : IFrostFSClient throw new ArgumentException($"Missed required parameter {nameof(options.Key)}"); } + _version = new FrostFsVersion(2, 13); + var nodesParams = AdjustNodeParams(options.NodeParams); var cache = new SessionCache(options.SessionExpirationDuration); @@ -642,6 +646,14 @@ public partial class Pool : IFrostFSClient return client.Client!.SearchObjectsAsync(args, ctx); } + public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) + { + if (header == null) + throw new ArgumentNullException(nameof(header)); + + return ObjectTools.CalculateObjectId(header, Owner, _version, Key); + } + public async Task GetBalanceAsync(CallContext ctx) { var client = Connection(); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 8561b50..133d2e3 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -565,7 +565,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl if (header.Split != null) { - ObjectTools.SetSplitValues(grpcHeader, header.Split, ClientContext); + ObjectTools.SetSplitValues(grpcHeader, header.Split, ClientContext.Owner, ClientContext.Version, ClientContext.Key); } var oid = new ObjectID { Value = grpcHeader.Sha256() }; diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index 58f9f30..fdc9a23 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -11,6 +11,20 @@ namespace FrostFS.SDK.Client; internal static class ObjectTools { + internal static FrostFsObjectId CalculateObjectId( + FrostFsObjectHeader header, + FrostFsOwner owner, + FrostFsVersion version, + ClientKey key) + { + var grpcHeader = CreateHeader(header, [], owner, version); + + if (header.Split != null) + SetSplitValues(grpcHeader, header.Split, owner, version, key); + + return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + } + internal static Object.Object CreateObject(FrostFsObject @object, ClientContext ctx) { @object.Header.OwnerId ??= ctx.Owner; @@ -24,7 +38,7 @@ internal static class ObjectTools var split = @object.Header.Split; if (split != null) { - SetSplitValues(grpcHeader, split, ctx); + SetSplitValues(grpcHeader, split, ctx.Owner, ctx.Version, ctx.Key); } var obj = new Object.Object @@ -43,13 +57,17 @@ internal static class ObjectTools return obj; } - internal static void SetSplitValues(Header grpcHeader, FrostFsSplit split, ClientContext ctx) + internal static void SetSplitValues( + Header grpcHeader, + FrostFsSplit split, + FrostFsOwner owner, FrostFsVersion version, + ClientKey key) { if (split == null) return; - if (ctx.Key == null) - throw new FrostFsInvalidObjectException(nameof(ctx.Key)); + if (key == null) + throw new FrostFsInvalidObjectException(nameof(key)); grpcHeader.Split = new Header.Types.Split { @@ -61,24 +79,24 @@ internal static class ObjectTools if (split.ParentHeader is not null) { - var grpcParentHeader = CreateHeader(split.ParentHeader, [], ctx); + var grpcParentHeader = CreateHeader(split.ParentHeader, [], owner, version); grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Signature { - Key = ctx.Key.PublicKeyProto, - Sign = ctx.Key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray()), + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignData(grpcHeader.Split.Parent.ToByteArray()), }; } grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, ClientContext ctx) + internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, FrostFsOwner owner, FrostFsVersion version) { - header.OwnerId ??= ctx.Owner; - header.Version ??= ctx.Version; + header.OwnerId ??= owner; + header.Version ??= version; var grpcHeader = header.GetHeader(); From db9b93b2e6886b539c8f507d46a83b61bc556b19 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Tue, 10 Dec 2024 15:01:28 +0300 Subject: [PATCH 36/65] [#28] Client: Move CalculateObjectId from IFrostFsClient to statis tools Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFSClient.cs | 8 ------ .../Interfaces/IFrostFSClient.cs | 2 -- src/FrostFS.SDK.Client/Pool/Pool.cs | 8 ------ src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 25 +++++++++++++++++-- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 9135f27..eea24fc 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -290,14 +290,6 @@ public class FrostFSClient : IFrostFSClient { return GetObjectService().SearchObjectsAsync(args, ctx); } - - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - return ObjectTools.CalculateObjectId(header, ClientCtx.Owner, ClientCtx.Version, ClientCtx.Key); - } #endregion #region Session Implementation diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 160e4e0..cce7364 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -65,6 +65,4 @@ public interface IFrostFSClient #endregion public Task Dial(CallContext ctx); - - FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header); } diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index 56ac3bf..3abfe60 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -646,14 +646,6 @@ public partial class Pool : IFrostFSClient return client.Client!.SearchObjectsAsync(args, ctx); } - public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - return ObjectTools.CalculateObjectId(header, Owner, _version, Key); - } - public async Task GetBalanceAsync(CallContext ctx) { var client = Connection(); diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index fdc9a23..7abdbaf 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using FrostFS.Object; @@ -9,14 +10,34 @@ using Google.Protobuf; namespace FrostFS.SDK.Client; -internal static class ObjectTools +public static class ObjectTools { - internal static FrostFsObjectId CalculateObjectId( + public static FrostFsObjectId CalculateObjectId( FrostFsObjectHeader header, FrostFsOwner owner, FrostFsVersion version, ClientKey key) { + if (header is null) + { + throw new ArgumentNullException(nameof(header)); + } + + if (owner is null) + { + throw new ArgumentNullException(nameof(owner)); + } + + if (version is null) + { + throw new ArgumentNullException(nameof(version)); + } + + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + var grpcHeader = CreateHeader(header, [], owner, version); if (header.Split != null) From 863751586925adc04664135fc2d6e9dd6683206e Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 11 Dec 2024 12:57:56 +0300 Subject: [PATCH 37/65] [#28] Client: Move CalculateObjectId from IFrostFsClient to statis tools Get payload hash as an argument Signed-off-by: Pavel Gross --- .../Services/ObjectServiceProvider.cs | 2 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 30 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 133d2e3..246c3eb 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -268,7 +268,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl internal async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) { - var grpcObject = ObjectTools.CreateObject(args.FrostFsObject, ClientContext); + var grpcObject = ObjectTools.CreateSingleObject(args.FrostFsObject, ClientContext); var request = new PutSingleRequest { diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index 7abdbaf..36a22a1 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -14,6 +14,7 @@ public static class ObjectTools { public static FrostFsObjectId CalculateObjectId( FrostFsObjectHeader header, + ReadOnlyMemory payloadHash, FrostFsOwner owner, FrostFsVersion version, ClientKey key) @@ -38,7 +39,7 @@ public static class ObjectTools throw new ArgumentNullException(nameof(key)); } - var grpcHeader = CreateHeader(header, [], owner, version); + var grpcHeader = CreateHeader(header, payloadHash, owner, version); if (header.Split != null) SetSplitValues(grpcHeader, header.Split, owner, version, key); @@ -46,7 +47,7 @@ public static class ObjectTools return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); } - internal static Object.Object CreateObject(FrostFsObject @object, ClientContext ctx) + internal static Object.Object CreateSingleObject(FrostFsObject @object, ClientContext ctx) { @object.Header.OwnerId ??= ctx.Owner; @object.Header.Version ??= ctx.Version; @@ -81,7 +82,8 @@ public static class ObjectTools internal static void SetSplitValues( Header grpcHeader, FrostFsSplit split, - FrostFsOwner owner, FrostFsVersion version, + FrostFsOwner owner, + FrostFsVersion version, ClientKey key) { if (split == null) @@ -100,7 +102,7 @@ public static class ObjectTools if (split.ParentHeader is not null) { - var grpcParentHeader = CreateHeader(split.ParentHeader, [], owner, version); + var grpcParentHeader = CreateHeader(split.ParentHeader, Array.Empty().Sha256(), owner, version); grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; grpcHeader.Split.ParentHeader = grpcParentHeader; @@ -114,16 +116,19 @@ public static class ObjectTools grpcHeader.Split.Previous = split.Previous?.ToMessage(); } - internal static Header CreateHeader(FrostFsObjectHeader header, byte[]? payload, FrostFsOwner owner, FrostFsVersion version) + internal static Header CreateHeader( + FrostFsObjectHeader header, + ReadOnlyMemory payloadChecksum, + FrostFsOwner owner, + FrostFsVersion version) { header.OwnerId ??= owner; header.Version ??= version; var grpcHeader = header.GetHeader(); - if (payload != null) // && payload.Length > 0 - grpcHeader.PayloadHash = Sha256Checksum(payload); - + grpcHeader.PayloadHash = ChecksumFromSha256(payloadChecksum); + return grpcHeader; } @@ -135,4 +140,13 @@ public static class ObjectTools Sum = ByteString.CopyFrom(data.Sha256()) }; } + + internal static Checksum ChecksumFromSha256(ReadOnlyMemory dataHash) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = UnsafeByteOperations.UnsafeWrap(dataHash) + }; + } } \ No newline at end of file From 568bdc67e872ff5244154ad08861fa7b819f62a1 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Tue, 24 Dec 2024 17:32:29 +0300 Subject: [PATCH 38/65] [#29] Client: Add object placement methods Signed-off-by: Pavel Gross --- .../Models/Netmap/FrostFsFilter.cs | 10 + .../Models/Netmap/FrostFsNetmapSnapshot.cs | 257 ++++++++++- .../Models/Netmap/FrostFsNodeInfo.cs | 76 ++- .../Models/Netmap/FrostFsPlacementPolicy.cs | 25 +- .../Models/Netmap/FrostFsReplica.cs | 18 +- .../Models/Netmap/FrostFsSelector.cs | 10 + .../Models/Netmap/IFrostFsFilter.cs | 11 + .../Models/Netmap/NodeAttrPair.cs | 7 + .../Models/Netmap/Placement/Clause.cs | 8 + .../Models/Netmap/Placement/Context.cs | 433 ++++++++++++++++++ .../Models/Netmap/Placement/HasherList.cs | 18 + .../Models/Netmap/Placement/IAggregator.cs | 8 + .../Models/Netmap/Placement/IHasher.cs | 6 + .../Models/Netmap/Placement/INormalizer.cs | 6 + .../Models/Netmap/Placement/MeanAgg.cs | 20 + .../Models/Netmap/Placement/MeanIQRAgg.cs | 65 +++ .../Models/Netmap/Placement/MinAgg.cs | 27 ++ .../Models/Netmap/Placement/Operation.cs | 16 + .../Models/Netmap/Placement/ReverseMinNorm.cs | 8 + .../Netmap/Placement/SelectFilterExpr.cs | 10 + .../Models/Netmap/Placement/SigmoidNorm.cs | 23 + .../Models/Netmap/Placement/Tools.cs | 140 ++++++ src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 23 +- src/FrostFS.SDK.Cryptography/Murmur3_128.cs | 28 +- .../Unit/PlacementVectorTests.cs | 161 +++++++ 25 files changed, 1382 insertions(+), 32 deletions(-) create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/IAggregator.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/IHasher.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/INormalizer.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/ReverseMinNorm.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/SelectFilterExpr.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/SigmoidNorm.cs create mode 100644 src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs new file mode 100644 index 0000000..ebd3c20 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK; + +public class FrostFsFilter(string name, string key, int operation, string value, FrostFsFilter[] filters) : IFrostFsFilter +{ + public string Name { get; } = name; + public string Key { get; } = key; + public int Operation { get; } = operation; + public string Value { get; } = value; + public FrostFsFilter[] Filters { get; } = filters; +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs index 3033ec6..686c966 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs @@ -1,4 +1,11 @@ +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Models.Netmap.Placement; +using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; @@ -7,4 +14,252 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n public ulong Epoch { get; private set; } = epoch; public IReadOnlyList NodeInfoCollection { get; private set; } = nodeInfoCollection; -} \ No newline at end of file + + internal static INormalizer NewReverseMinNorm(double minV) + { + return new ReverseMinNorm { min = minV }; + } + + // newSigmoidNorm returns a normalizer which + // normalize values in range of 0.0 to 1.0 to a scaled sigmoid. + internal static INormalizer NewSigmoidNorm(double scale) + { + return new SigmoidNorm(scale); + } + + // PlacementVectors sorts container nodes returned by ContainerNodes method + // and returns placement vectors for the entity identified by the given pivot. + // For example, in order to build node list to store the object, binary-encoded + // object identifier can be used as pivot. Result is deterministic for + // the fixed NetMap and parameters. + public FrostFsNodeInfo[][] PlacementVectors(FrostFsNodeInfo[][] vectors, byte[] pivot) + { + if (vectors is null) + { + throw new ArgumentNullException(nameof(vectors)); + } + + using var murmur3 = new Murmur3(0); + var hash = murmur3.GetCheckSum64(pivot); + + var wf = Tools.DefaultWeightFunc(NodeInfoCollection.ToArray()); + + var result = new FrostFsNodeInfo[vectors.Length][]; + var maxSize = vectors.Max(x => x.Length); + + var spanWeigths = new double[maxSize]; + + for (int i = 0; i < vectors.Length; i++) + { + result[i] = new FrostFsNodeInfo[vectors[i].Length]; + + for (int j = 0; j < vectors[i].Length; j++) + { + result[i][j] = vectors[i][j]; + } + + Tools.AppendWeightsTo(result[i], wf, spanWeigths); + + Tools.SortHasherSliceByWeightValue(result[i].ToList(), spanWeigths, hash); + } + + return result; + } + + // SelectFilterNodes returns a two-dimensional list of nodes as a result of applying the + // given SelectFilterExpr to the NetMap. + // If the SelectFilterExpr contains only filters, the result contains a single row with the + // result of the last filter application. + // If the SelectFilterExpr contains only selectors, the result contains the selection rows + // of the last select application. + List> SelectFilterNodes(SelectFilterExpr expr) + { + var policy = new FrostFsPlacementPolicy(false); + + foreach (var f in expr.Filters) + policy.Filters.Add(f); + + policy.Selectors.Add(expr.Selector); + + var ctx = new Context(this) + { + Cbf = expr.Cbf + }; + + ctx.ProcessFilters(policy); + ctx.ProcessSelectors(policy); + + var ret = new List>(); + + if (expr.Selector == null) + { + var lastFilter = expr.Filters[^1]; + + var subCollestion = new List(); + ret.Add(subCollestion); + + foreach (var nodeInfo in NodeInfoCollection) + { + if (ctx.Match(ctx.ProcessedFilters[lastFilter.Name], nodeInfo)) + { + subCollestion.Add(nodeInfo); + } + } + } + else if (expr.Selector.Name != null) + { + var sel = ctx.GetSelection(ctx.ProcessedSelectors[expr.Selector.Name]); + + foreach (var ns in sel) + { + var subCollestion = new List(); + ret.Add(subCollestion); + foreach (var n in ns) + { + subCollestion.Add(n); + } + } + } + + return ret; + } + + internal static Func NewWeightFunc(INormalizer capNorm, INormalizer priceNorm) + { + return new Func((FrostFsNodeInfo nodeInfo) => + { + return capNorm.Normalize(nodeInfo.GetCapacity()) * priceNorm.Normalize(nodeInfo.Price); + }); + } + + private static FrostFsNodeInfo[] FlattenNodes(List> nodes) + { + int sz = 0; + foreach (var ns in nodes) + { + sz += ns.Count; + } + + var result = new FrostFsNodeInfo[sz]; + + int i = 0; + foreach (var ns in nodes) + { + foreach (var n in ns) + { + result[i++] = n; + } + } + + return result; + } + + // ContainerNodes returns two-dimensional list of nodes as a result of applying + // given PlacementPolicy to the NetMap. Each line of the list corresponds to a + // replica descriptor. Line order corresponds to order of ReplicaDescriptor list + // in the policy. Nodes are pre-filtered according to the Filter list from + // the policy, and then selected by Selector list. Result is deterministic for + // the fixed NetMap and parameters. + // + // Result can be used in PlacementVectors. + public FrostFsNodeInfo[][] ContainerNodes(FrostFsPlacementPolicy p, byte[]? pivot) + { + var c = new Context(this) + { + Cbf = p.BackupFactor + }; + + if (pivot != null && pivot.Length > 0) + { + c.HrwSeed = pivot; + + using var murmur = new Murmur3(0); + c.HrwSeedHash = murmur.GetCheckSum64(pivot); + } + + c.ProcessFilters(p); + c.ProcessSelectors(p); + + var unique = p.IsUnique(); + + var result = new List>(p.Replicas.Length); + for (int i = 0; i < p.Replicas.Length; i++) + { + result.Add([]); + } + + // Note that the cached selectors are not used when the policy contains the UNIQUE flag. + // This is necessary because each selection vector affects potentially the subsequent vectors + // and thus we call getSelection in such case, in order to take into account nodes previously + // marked as used by earlier replicas. + for (int i = 0; i < p.Replicas.Length; i++) + { + var sName = p.Replicas[i].Selector; + + if (string.IsNullOrEmpty(sName) && !(p.Replicas.Length == 1 && p.Selectors.Count == 1)) + { + var s = new FrostFsSelector(Context.mainFilterName) + { + Count = p.Replicas[i].CountNodes() + }; + + var nodes = c.GetSelection(s); + + var arg = new List>(nodes.Count); + for (int j = 0; j < nodes.Count; j++) + { + arg[i] = nodes[j]; + } + + result[i].AddRange(FlattenNodes(arg)); + + if (unique) + { + foreach (var n in result[i]) + { + c.UsedNodes[n.Hash()] = true; + } + } + + continue; + } + + if (unique) + { + if (!c.ProcessedSelectors.TryGetValue(sName, out var s) || s == null) + { + throw new FrostFsException($"selector not found: {sName}"); + } + + var nodes = c.GetSelection(c.ProcessedSelectors[sName]); + + result[i].AddRange(FlattenNodes(nodes)); + + foreach (var n in result[i]) + { + c.UsedNodes[n.Hash()] = true; + } + } + else + { + var nodes = c.Selections[sName]; + + var arg = new List>(nodes.Count); + for (int j = 0; j < nodes.Count; j++) + { + arg[i] = nodes[j]; + } + + result[i].AddRange(FlattenNodes(arg)); + } + } + + var collection = new FrostFsNodeInfo[result.Count][]; + for(int i =0; i < result.Count; i++) + { + collection[i] = [.. result[i]]; + } + + return collection; + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs index 9581b21..ea7d7c5 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNodeInfo.cs @@ -1,5 +1,9 @@ using System; using System.Collections.Generic; +using System.Globalization; + +using FrostFS.SDK.Client.Models.Netmap.Placement; +using FrostFS.SDK.Cryptography; namespace FrostFS.SDK; @@ -8,11 +12,69 @@ public class FrostFsNodeInfo( NodeState state, IReadOnlyCollection addresses, IReadOnlyDictionary attributes, - ReadOnlyMemory publicKey) + ReadOnlyMemory publicKey) : IHasher { - public NodeState State { get; private set; } = state; - public FrostFsVersion Version { get; private set; } = version; - public IReadOnlyCollection Addresses { get; private set; } = addresses; - public IReadOnlyDictionary Attributes { get; private set; } = attributes; - public ReadOnlyMemory PublicKey { get; private set; } = publicKey; -} \ No newline at end of file + private ulong _hash; + + // attrPrice is a key to the node attribute that indicates the + // price in GAS tokens for storing one GB of data during one Epoch. + internal const string AttrPrice = "Price"; + + // attrCapacity is a key to the node attribute that indicates the + // total available disk space in Gigabytes. + internal const string AttrCapacity = "Capacity"; + + // attrExternalAddr is a key for the attribute storing node external addresses. + internal const string AttrExternalAddr = "ExternalAddr"; + + // sepExternalAddr is a separator for multi-value ExternalAddr attribute. + internal const string SepExternalAddr = ","; + + private ulong price = ulong.MaxValue; + + public NodeState State { get; } = state; + + public FrostFsVersion Version { get; } = version; + + public IReadOnlyCollection Addresses { get; } = addresses; + + public IReadOnlyDictionary Attributes { get; } = attributes; + + public ReadOnlyMemory PublicKey { get; } = publicKey; + + public ulong Hash() + { + if (_hash == 0) + { + using var murmur3 = new Murmur3(0); + murmur3.Initialize(); + _hash = murmur3.GetCheckSum64(PublicKey.ToArray()); + } + + return _hash; + } + + internal ulong GetCapacity() + { + if (!Attributes.TryGetValue(AttrCapacity, out var val)) + return 0; + + return ulong.Parse(val, CultureInfo.InvariantCulture); + } + + internal ulong Price + { + get + { + if (price == ulong.MaxValue) + { + if (!Attributes.TryGetValue(AttrPrice, out var val)) + price = 0; + else + price = uint.Parse(val, CultureInfo.InvariantCulture); + } + + return price; + } + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index 29c9310..19fe755 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -1,5 +1,5 @@ - using System; +using System.Collections.ObjectModel; using System.Linq; using FrostFS.Netmap; @@ -13,12 +13,21 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replic private PlacementPolicy policy; public FrostFsReplica[] Replicas { get; private set; } = replicas; + + public Collection Selectors { get; } = []; + + public Collection Filters { get; } = []; + public bool Unique { get; private set; } = unique; + public uint BackupFactor { get; set; } + public override readonly bool Equals(object obj) { if (obj is null) + { return false; + } var other = (FrostFsPlacementPolicy)obj; @@ -46,14 +55,10 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replic return policy; } - //public static FrostFsPlacementPolicy ToModel(placementPolicy) - //{ - // return new FrostFsPlacementPolicy( - // placementPolicy.Unique, - // placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() - // ); - //} - + internal readonly bool IsUnique() + { + return Unique || Replicas.Any(r => r.EcDataCount != 0 || r.EcParityCount != 0); + } public override readonly int GetHashCode() { @@ -86,4 +91,4 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replic return true; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs index fa2f8db..5ace048 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs @@ -6,6 +6,8 @@ public struct FrostFsReplica : IEquatable { public int Count { get; set; } public string Selector { get; set; } + public uint EcDataCount { get; set; } + public uint EcParityCount { get; set; } public FrostFsReplica(int count, string? selector = null) { @@ -18,16 +20,23 @@ public struct FrostFsReplica : IEquatable public override readonly bool Equals(object obj) { if (obj is null) + { return false; + } var other = (FrostFsReplica)obj; return Count == other.Count && Selector == other.Selector; } + public readonly uint CountNodes() + { + return Count != 0 ? (uint)Count : EcDataCount + EcParityCount; + } + public override readonly int GetHashCode() { - return Count + Selector.GetHashCode(); + return (Count + Selector.GetHashCode()) ^ (int)EcDataCount ^ (int)EcParityCount; } public static bool operator ==(FrostFsReplica left, FrostFsReplica right) @@ -42,6 +51,9 @@ public struct FrostFsReplica : IEquatable public readonly bool Equals(FrostFsReplica other) { - return Count == other.Count && Selector == other.Selector; + return Count == other.Count + && Selector == other.Selector + && EcDataCount == other.EcDataCount + && EcParityCount == other.EcParityCount; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs new file mode 100644 index 0000000..74380f8 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs @@ -0,0 +1,10 @@ +namespace FrostFS.SDK; + +public class FrostFsSelector(string name) +{ + public string Name { get; } = name; + public uint Count { get; set; } + public uint Clause { get; set; } + public string? Attribute { get; set; } + public string? Filter { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs b/src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs new file mode 100644 index 0000000..e13875a --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/IFrostFsFilter.cs @@ -0,0 +1,11 @@ +namespace FrostFS.SDK +{ + public interface IFrostFsFilter + { + FrostFsFilter[] Filters { get; } + string Key { get; } + string Name { get; } + int Operation { get; } + string Value { get; } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs b/src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs new file mode 100644 index 0000000..3c014fc --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/NodeAttrPair.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK; + +struct NodeAttrPair +{ + internal string attr; + internal FrostFsNodeInfo[] nodes; +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs new file mode 100644 index 0000000..b982027 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Clause.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +public enum FrostFsClause +{ + Unspecified = 0, + Same, + Distinct +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs new file mode 100644 index 0000000..fc6e80b --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct Context +{ + private const string errInvalidFilterName = "filter name is invalid"; + private const string errInvalidFilterOp = "invalid filter operation"; + private const string errFilterNotFound = "filter not found"; + private const string errNonEmptyFilters = "simple filter contains sub-filters"; + private const string errNotEnoughNodes = "not enough nodes to SELECT from"; + private const string errUnnamedTopFilter = "unnamed top-level filter"; + + internal const string mainFilterName = "*"; + internal const string likeWildcard = "*"; + + // network map to operate on + internal FrostFsNetmapSnapshot NetMap { get; } + + // cache of processed filters + internal Dictionary ProcessedFilters { get; } = []; + + // cache of processed selectors + internal Dictionary ProcessedSelectors { get; } = []; + + // stores results of selector processing + internal Dictionary>> Selections { get; } = []; + + // cache of parsed numeric values + internal Dictionary NumCache { get; } = []; + + internal byte[]? HrwSeed { get; set; } + + // hrw.Hash of hrwSeed + internal ulong HrwSeedHash { get; set; } + + // container backup factor + internal uint Cbf { get; set; } + + // nodes already used in previous selections, which is needed when the placement + // policy uses the UNIQUE flag. Nodes marked as used are not used in subsequent + // base selections. + internal Dictionary UsedNodes { get; } = []; + + // If true, returns an error when netmap does not contain enough nodes for selection. + // By default best effort is taken. + internal bool Strict { get; set; } + + // weightFunc is a weighting function for determining node priority + // which combines low price and high performance + private readonly Func weightFunc; + + public Context(FrostFsNetmapSnapshot netMap) + { + NetMap = netMap; + weightFunc = Tools.DefaultWeightFunc(NetMap.NodeInfoCollection); + } + + internal void ProcessFilters(FrostFsPlacementPolicy policy) + { + foreach (var filter in policy.Filters) + { + ProcessFilter(filter, true); + } + } + + readonly void ProcessFilter(FrostFsFilter filter, bool top) + { + var filterName = filter.Name; + if (filterName == mainFilterName) + { + throw new FrostFsException($"{errInvalidFilterName}: '{errInvalidFilterName}' is reserved"); + } + + if (top && string.IsNullOrEmpty(filterName)) + { + throw new FrostFsException(errUnnamedTopFilter); + } + + if (!top && !string.IsNullOrEmpty(filterName) && !ProcessedFilters.ContainsKey(filterName)) + { + throw new FrostFsException(errFilterNotFound); + } + + if (filter.Operation == (int)Operation.AND || + filter.Operation == (int)Operation.OR || + filter.Operation == (int)Operation.NOT) + { + foreach (var f in filter.Filters) + ProcessFilter(f, false); + } + else + { + if (filter.Filters.Length != 0) + { + throw new FrostFsException(errNonEmptyFilters); + } + else if (!top && !string.IsNullOrEmpty(filterName)) + { + // named reference + return; + } + + if (filter.Operation == (int)Operation.EQ || + filter.Operation == (int)Operation.NE || + filter.Operation == (int)Operation.LIKE || + filter.Operation == (int)Operation.GT || + filter.Operation == (int)Operation.GE || + filter.Operation == (int)Operation.LT || + filter.Operation == (int)Operation.LE) + { + var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture); + NumCache[filter.Value] = n; + } + else + { + throw new FrostFsException($"{errInvalidFilterOp}: {filter.Operation}"); + } + } + + if (top) + { + ProcessedFilters[filterName] = filter; + } + } + + // processSelectors processes selectors and returns error is any of them is invalid. + internal void ProcessSelectors(FrostFsPlacementPolicy policy) + { + foreach (var selector in policy.Selectors) + { + var filterName = selector.Filter; + if (filterName != mainFilterName) + { + if (selector.Filter == null || !ProcessedFilters.ContainsKey(selector.Filter)) + { + throw new FrostFsException($"{errFilterNotFound}: SELECT FROM '{filterName}'"); + } + } + + ProcessedSelectors[selector.Name] = selector; + + var selection = GetSelection(selector); + + Selections[selector.Name] = selection; + } + } + + // calcNodesCount returns number of buckets and minimum number of nodes in every bucket + // for the given selector. + static (int bucketCount, int nodesInBucket) CalcNodesCount(FrostFsSelector selector) + { + return selector.Clause == (uint)FrostFsClause.Same + ? (1, (int)selector.Count) + : ((int)selector.Count, 1); + } + + // getSelectionBase returns nodes grouped by selector attribute. + // It it guaranteed that each pair will contain at least one node. + internal NodeAttrPair[] GetSelectionBase(FrostFsSelector selector) + { + var fName = selector.Filter ?? throw new FrostFsException("Filter name for selector is empty"); + + _ = ProcessedFilters.TryGetValue(fName, out var f); + + var isMain = fName == mainFilterName; + var result = new List(); + + var nodeMap = new Dictionary>(); + var attr = selector.Attribute; + + foreach (var node in NetMap.NodeInfoCollection) + { + if (UsedNodes.ContainsKey(node.Hash())) + { + continue; + } + + if (isMain || Match(f, node)) + { + if (attr == null) + { + // Default attribute is transparent identifier which is different for every node. + result.Add(new NodeAttrPair { attr = "", nodes = [node] }); + } + else + { + var v = node.Attributes[attr]; + if (!nodeMap.TryGetValue(v, out var nodes) || nodes == null) + { + nodeMap[v] = []; + } + + nodeMap[v].Add(node); + } + } + } + + if (!string.IsNullOrEmpty(attr)) + { + foreach (var v in nodeMap) + { + result.Add(new NodeAttrPair() { attr = v.Key, nodes = [.. v.Value] }); + } + } + + if (HrwSeed != null && HrwSeed.Length != 0) + { + double[] ws = []; + + foreach (var res in result) + { + Tools.AppendWeightsTo(res.nodes, weightFunc, ws); + Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash); + } + } + return [.. result]; + } + + static double CalcBucketWeight(List ns, IAggregator a, Func wf) + { + foreach (var node in ns) + { + a.Add(wf(node)); + } + + return a.Compute(); + } + + // getSelection returns nodes grouped by s.attribute. + // Last argument specifies if more buckets can be used to fulfill CBF. + internal List> GetSelection(FrostFsSelector s) + { + var (bucketCount, nodesInBucket) = CalcNodesCount(s); + + var buckets = GetSelectionBase(s); + + if (Strict && buckets.Length < bucketCount) + throw new FrostFsException($"errNotEnoughNodes: '{s.Name}'"); + + // We need deterministic output in case there is no pivot. + // If pivot is set, buckets are sorted by HRW. + // However, because initial order influences HRW order for buckets with equal weights, + // we also need to have deterministic input to HRW sorting routine. + if (HrwSeed == null || HrwSeed.Length == 0) + { + buckets = string.IsNullOrEmpty(s.Attribute) + ? [.. buckets.OrderBy(b => b.nodes[0].Hash())] + : [.. buckets.OrderBy(b => b.attr)]; + } + + var maxNodesInBucket = nodesInBucket * (int)Cbf; + + var res = new List>(buckets.Length); + var fallback = new List>(buckets.Length); + + for (int i = 0; i < buckets.Length; i++) + { + var ns = buckets[i].nodes; + if (ns.Length >= maxNodesInBucket) + { + res.Add(new List(ns[..maxNodesInBucket])); + } + else if (ns.Length >= nodesInBucket) + { + fallback.Add(new List(ns)); + } + } + + if (res.Count < bucketCount) + { + // Fallback to using minimum allowed backup factor (1). + res = fallback; + + if (Strict && res.Count < bucketCount) + { + throw new FrostFsException($"{errNotEnoughNodes}: {s}"); + } + } + + if (HrwSeed != null && HrwSeed.Length != 0) + { + var weights = new double[res.Count]; + var a = new MeanIQRAgg(); + + for (int i = 0; i < res.Count; i++) + { + a.Clear(); + weights[i] = CalcBucketWeight(res[i], a, weightFunc); + } + + var hashers = res.Select(r => new HasherList(r)).ToList(); + Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash); + } + + if (res.Count < bucketCount) + { + if (Strict && res.Count == 0) + { + throw new FrostFsException(errNotEnoughNodes); + } + + bucketCount = res.Count; + } + + if (string.IsNullOrEmpty(s.Attribute)) + { + fallback = res.Skip(bucketCount).ToList(); + res = res.Take(bucketCount).ToList(); + + for (int i = 0; i < fallback.Count; i++) + { + var index = i % bucketCount; + if (res[index].Count >= maxNodesInBucket) + { + break; + } + + res[index].AddRange(fallback[i]); + } + } + + return res.Take(bucketCount).ToList(); + } + + internal bool MatchKeyValue(FrostFsFilter f, FrostFsNodeInfo nodeInfo) + { + switch (f.Operation) + { + case (int)Operation.EQ: + return nodeInfo.Attributes[f.Key] == f.Value; + case (int)Operation.LIKE: + { + var hasPrefix = f.Value.StartsWith(likeWildcard, StringComparison.Ordinal); + var hasSuffix = f.Value.EndsWith(likeWildcard, StringComparison.Ordinal); + + var start = hasPrefix ? likeWildcard.Length : 0; + var end = hasSuffix ? f.Value.Length - likeWildcard.Length : f.Value.Length; + var str = f.Value[start..end]; + + if (hasPrefix && hasSuffix) + return nodeInfo.Attributes[f.Key].Contains(str); + + if (hasPrefix && !hasSuffix) + return nodeInfo.Attributes[f.Key].EndsWith(str, StringComparison.Ordinal); + + if (!hasPrefix && hasSuffix) + return nodeInfo.Attributes[f.Key].StartsWith(str, StringComparison.Ordinal); + + + return nodeInfo.Attributes[f.Key] == f.Value; + } + case (int)Operation.NE: + return nodeInfo.Attributes[f.Key] != f.Value; + default: + { + var attr = f.Key switch + { + FrostFsNodeInfo.AttrPrice => nodeInfo.Price, + FrostFsNodeInfo.AttrCapacity => nodeInfo.GetCapacity(), + _ => uint.Parse(nodeInfo.Attributes[f.Key], CultureInfo.InvariantCulture), + }; + + switch (f.Operation) + { + case (int)Operation.GT: + return attr > NumCache[f.Value]; + case (int)Operation.GE: + return attr >= NumCache[f.Value]; + case (int)Operation.LT: + return attr < NumCache[f.Value]; + case (int)Operation.LE: + return attr <= NumCache[f.Value]; + default: + // do nothing and return false + break; + } + } + break; + } + + // will not happen if context was created from f (maybe panic?) + return false; + } + + // match matches f against b. It returns no errors because + // filter should have been parsed during context creation + // and missing node properties are considered as a regular fail. + internal bool Match(FrostFsFilter f, FrostFsNodeInfo nodeInfo) + { + switch (f.Operation) + { + case (int)Operation.NOT: + { + var inner = f.Filters; + var fSub = inner[0]; + + if (!string.IsNullOrEmpty(inner[0].Name)) + { + fSub = ProcessedFilters[inner[0].Name]; + } + return !Match(fSub, nodeInfo); + } + case (int)Operation.AND: + case (int)Operation.OR: + { + for (int i = 0; i < f.Filters.Length; i++) + { + var fSub = f.Filters[i]; + + if (!string.IsNullOrEmpty(f.Filters[i].Name)) + { + fSub = ProcessedFilters[f.Filters[i].Name]; + } + + var ok = Match(fSub, nodeInfo); + + if (ok == (f.Operation == (int)Operation.OR)) + { + return ok; + } + } + + return f.Operation == (int)Operation.AND; + } + default: + return MatchKeyValue(f, nodeInfo); + } + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs new file mode 100644 index 0000000..117980c --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal sealed class HasherList : IHasher +{ + private readonly List _nodes; + + internal HasherList(List nodes) + { + _nodes = nodes; + } + + public ulong Hash() + { + return _nodes.Count > 0 ? _nodes[0].Hash() : 0; + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/IAggregator.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/IAggregator.cs new file mode 100644 index 0000000..774dbc2 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/IAggregator.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal interface IAggregator +{ + void Add(double d); + + double Compute(); +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/IHasher.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/IHasher.cs new file mode 100644 index 0000000..6a0f536 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/IHasher.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal interface IHasher +{ + ulong Hash(); +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/INormalizer.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/INormalizer.cs new file mode 100644 index 0000000..ddf4169 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/INormalizer.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +interface INormalizer +{ + double Normalize(double w); +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs new file mode 100644 index 0000000..0febba1 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs @@ -0,0 +1,20 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct MeanAgg +{ + private double mean; + private int count; + + internal void Add(double n) + { + int c = count + 1; + mean *= (double)count / c + n / c; + + count++; + } + + internal readonly double Compute() + { + return mean; + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs new file mode 100644 index 0000000..e7f6fca --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanIQRAgg.cs @@ -0,0 +1,65 @@ +using System.Collections.ObjectModel; +using System.Linq; + +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct MeanIQRAgg : IAggregator +{ + private const int minLn = 4; + internal Collection arr = []; + + public MeanIQRAgg() + { + } + + public readonly void Add(double d) + { + arr.Add(d); + } + + public readonly double Compute() + { + var length = arr.Count; + if (length == 0) + { + return 0; + } + + var sorted = arr.OrderBy(p => p).ToArray(); + + double minV, maxV; + + if (arr.Count < minLn) + { + minV = sorted[0]; + maxV = sorted[length - 1]; + } + else + { + var start = length / minLn; + var end = length * 3 / minLn - 1; + + minV = sorted[start]; + maxV = sorted[end]; + } + + var count = 0; + double sum = 0; + + foreach (var e in sorted) + { + if (e >= minV && e <= maxV) + { + sum += e; + count++; + } + } + + return sum / count; + } + + internal readonly void Clear() + { + arr.Clear(); + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs new file mode 100644 index 0000000..7b68fc0 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MinAgg.cs @@ -0,0 +1,27 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct MinAgg +{ + private double min; + private bool minFound; + + internal void Add(double n) + { + if (!minFound) + { + min = n; + minFound = true; + return; + } + + if (n < min) + { + min = n; + } + } + + internal readonly double Compute() + { + return min; + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs new file mode 100644 index 0000000..705827e --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Operation.cs @@ -0,0 +1,16 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +public enum Operation +{ + Unspecified = 0, + EQ, + NE, + GT, + GE, + LT, + LE, + OR, + AND, + NOT, + LIKE +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/ReverseMinNorm.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/ReverseMinNorm.cs new file mode 100644 index 0000000..d3ffe5c --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/ReverseMinNorm.cs @@ -0,0 +1,8 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct ReverseMinNorm : INormalizer +{ + internal double min; + + public readonly double Normalize(double w) => (min + 1) / (w + 1); +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/SelectFilterExpr.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/SelectFilterExpr.cs new file mode 100644 index 0000000..22cb961 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/SelectFilterExpr.cs @@ -0,0 +1,10 @@ +using System.Collections.ObjectModel; + +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal struct SelectFilterExpr(uint cbf, FrostFsSelector selector, Collection filters) +{ + internal uint Cbf { get; } = cbf; + internal FrostFsSelector Selector { get; } = selector; + internal Collection Filters { get; } = filters; +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/SigmoidNorm.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/SigmoidNorm.cs new file mode 100644 index 0000000..e4cd416 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/SigmoidNorm.cs @@ -0,0 +1,23 @@ +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +internal readonly struct SigmoidNorm : INormalizer +{ + private readonly double _scale; + + internal SigmoidNorm(double scale) + { + _scale = scale; + } + + public readonly double Normalize(double w) + { + if (_scale == 0) + { + return 0; + } + + var x = w / _scale; + + return x / (1 + x); + } +} diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs new file mode 100644 index 0000000..b6de113 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using static FrostFS.SDK.FrostFsNetmapSnapshot; + +namespace FrostFS.SDK.Client.Models.Netmap.Placement; + +public static class Tools +{ + internal static ulong Distance(ulong x, ulong y) + { + var acc = x ^ y; + acc ^= acc >> 33; + acc *= 0xff51afd7ed558ccd; + acc ^= acc >> 33; + acc *= 0xc4ceb9fe1a85ec53; + acc ^= acc >> 33; + + return acc; + } + + internal static double ReverceNormalize(double r, double w) + { + return (r + 1) / (w + 1); + } + + internal static double Normalize(double r, double w) + { + if (r == 0) + { + return 0; + } + + var x = w / r; + return x / (1 + x); + } + + internal static void AppendWeightsTo(FrostFsNodeInfo[] nodes, Func wf, double[] weights) + { + if (weights.Length < nodes.Length) + { + weights = new double[nodes.Length]; + } + + for (int i = 0; i < nodes.Length; i++) + { + weights[i] = wf(nodes[i]); + } + } + + internal static void SortHasherSliceByWeightValue(List nodes, Span weights, ulong hash) where T : IHasher + { + if (nodes.Count == 0) + { + return; + } + + var allEquals = true; + + if (weights.Length > 1) + { + for (int i = 1; i < weights.Length; i++) + { + if (weights[i] != weights[0]) + { + allEquals = false; + break; + } + } + } + + var dist = new ulong[nodes.Count]; + + if (allEquals) + { + for (int i = 0; i < dist.Length; i++) + { + var x = nodes[i].Hash(); + dist[i] = Distance(x, hash); + } + + SortHasherByDistance(nodes, dist, true); + return; + } + + for (int i = 0; i < dist.Length; i++) + { + var d = Distance(nodes[i].Hash(), hash); + dist[i] = ulong.MaxValue - (ulong)(d * weights[i]); + } + + SortHasherByDistance(nodes, dist, false); + } + + internal static void SortHasherByDistance(List nodes, N[] dist, bool asc) + { + IndexedValue[] indexes = new IndexedValue[nodes.Count]; + for (int i = 0; i < dist.Length; i++) + { + indexes[i] = new IndexedValue() { nodeInfo = nodes[i], dist = dist[i] }; + } + + if (asc) + { + nodes = new List(indexes + .OrderBy(x => x.dist) + .Select(x => x.nodeInfo).ToArray()); + } + else + { + nodes = new List(indexes + .OrderByDescending(x => x.dist) + .Select(x => x.nodeInfo) + .ToArray()); + } + } + + internal static Func DefaultWeightFunc(IReadOnlyList nodes) + { + MeanAgg mean = new(); + MinAgg minV = new(); + + foreach (var node in nodes) + { + mean.Add(node.GetCapacity()); + minV.Add(node.Price); + } + + return NewWeightFunc( + NewSigmoidNorm(mean.Compute()), + NewReverseMinNorm(minV.Compute())); + } + + private struct IndexedValue + { + internal T nodeInfo; + internal N dist; + } +} diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index 36a22a1..40cf1ad 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -13,7 +13,7 @@ namespace FrostFS.SDK.Client; public static class ObjectTools { public static FrostFsObjectId CalculateObjectId( - FrostFsObjectHeader header, + FrostFsObjectHeader header, ReadOnlyMemory payloadHash, FrostFsOwner owner, FrostFsVersion version, @@ -58,6 +58,7 @@ public static class ObjectTools grpcHeader.PayloadHash = Sha256Checksum(@object.SingleObjectPayload); var split = @object.Header.Split; + if (split != null) { SetSplitValues(grpcHeader, split, ctx.Owner, ctx.Version, ctx.Key); @@ -80,17 +81,21 @@ public static class ObjectTools } internal static void SetSplitValues( - Header grpcHeader, + Header grpcHeader, FrostFsSplit split, - FrostFsOwner owner, - FrostFsVersion version, + FrostFsOwner owner, + FrostFsVersion version, ClientKey key) { if (split == null) + { return; + } if (key == null) + { throw new FrostFsInvalidObjectException(nameof(key)); + } grpcHeader.Split = new Header.Types.Split { @@ -98,7 +103,9 @@ public static class ObjectTools }; if (split.Children != null && split.Children.Count != 0) + { grpcHeader.Split.Children.AddRange(split.Children.Select(id => id.ToMessage())); + } if (split.ParentHeader is not null) { @@ -118,8 +125,8 @@ public static class ObjectTools internal static Header CreateHeader( FrostFsObjectHeader header, - ReadOnlyMemory payloadChecksum, - FrostFsOwner owner, + ReadOnlyMemory payloadChecksum, + FrostFsOwner owner, FrostFsVersion version) { header.OwnerId ??= owner; @@ -128,7 +135,7 @@ public static class ObjectTools var grpcHeader = header.GetHeader(); grpcHeader.PayloadHash = ChecksumFromSha256(payloadChecksum); - + return grpcHeader; } @@ -149,4 +156,4 @@ public static class ObjectTools Sum = UnsafeByteOperations.UnsafeWrap(dataHash) }; } -} \ No newline at end of file +} diff --git a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs index e8f52fc..3e0ae5f 100644 --- a/src/FrostFS.SDK.Cryptography/Murmur3_128.cs +++ b/src/FrostFS.SDK.Cryptography/Murmur3_128.cs @@ -4,7 +4,7 @@ using System.Security.Cryptography; namespace FrostFS.SDK.Cryptography; -internal class Murmur3_128 : HashAlgorithm +public class Murmur3 : HashAlgorithm { private const ulong c1 = 0x87c37b91114253d5; private const ulong c2 = 0x4cf5ad432745937f; @@ -17,14 +17,31 @@ internal class Murmur3_128 : HashAlgorithm private readonly uint seed; private int length; - public Murmur3_128(uint seed) + public Murmur3(uint seed) { this.seed = seed; Initialize(); } + public ulong GetCheckSum64(byte[] bytes) + { + if (bytes is null) + { + throw new ArgumentNullException(nameof(bytes)); + } + + Initialize(); + HashCore(bytes, 0, bytes.Length); + return HashFinalUlong(); + } + protected override void HashCore(byte[] array, int ibStart, int cbSize) { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + length += cbSize; int remainder = cbSize & 15; int alignedLength = ibStart + (cbSize - remainder); @@ -92,6 +109,11 @@ internal class Murmur3_128 : HashAlgorithm } protected override byte[] HashFinal() + { + return BitConverter.GetBytes(HashFinalUlong()); + } + + protected ulong HashFinalUlong() { h1 ^= (ulong)length; h2 ^= (ulong)length; @@ -102,7 +124,7 @@ internal class Murmur3_128 : HashAlgorithm h1 += h2; h2 += h1; - return BitConverter.GetBytes(h1); + return h1; } public override void Initialize() diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs new file mode 100644 index 0000000..e578f90 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs @@ -0,0 +1,161 @@ +using System.Diagnostics.CodeAnalysis; + +using FrostFS.SDK.Client.Models.Netmap.Placement; + +namespace FrostFS.SDK.Tests.Unit; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +public class PlacementVectorTests +{ + Dictionary[] attribs; + + public PlacementVectorTests() + { + var attribs1 = new Dictionary + { + {"Country", "Germany" }, + {"Price", "2" }, + {"Capacity", "10000"} + }; + + var attribs2 = new Dictionary + { + {"Country", "Germany" }, + {"Price", "4" }, + {"Capacity", "1"} + }; + + var attribs3 = new Dictionary + { + {"Country", "France" }, + {"Price", "3" }, + {"Capacity", "10"} + }; + + var attribs4 = new Dictionary + { + {"Country", "Russia" }, + {"Price", "2" }, + {"Capacity", "10000"} + }; + + var attribs5 = new Dictionary + { + {"Country", "Russia" }, + {"Price", "1" }, + {"Capacity", "10000"} + }; + + var attribs6 = new Dictionary + { + {"Country", "Russia" }, + {"Capacity", "10000"} + }; + var attribs7 = new Dictionary + { + {"Country", "France" }, + {"Price", "100" }, + {"Capacity", "10000"} + }; + var attribs8 = new Dictionary + { + {"Country", "France" }, + {"Price", "7" }, + {"Capacity", "10000"} + }; + var attribs9 = new Dictionary + { + {"Country", "Russia" }, + {"Price", "2" }, + {"Capacity", "1"} + }; + + attribs = [attribs1, attribs2, attribs3, attribs4, attribs5, attribs6, attribs7, attribs8, attribs9]; + } + + [Fact] + public void PlacementVectorTest() + { + FrostFsVersion v = new(2, 13); + var addresses = new string[] { "localhost", "server1" }; + var key1 = new byte[] { 1 }; + var key2 = new byte[] { 2 }; + var key3 = new byte[] { 3 }; + + var nodes = new List + { + new(v, NodeState.Online, addresses.AsReadOnly(), attribs[5].AsReadOnly(), key1), + new(v, NodeState.Online, addresses.AsReadOnly(), attribs[0].AsReadOnly(), key2), + new(v, NodeState.Online, addresses.AsReadOnly(), attribs[8].AsReadOnly(), key3) + }; + + var netmap = new FrostFsNetmapSnapshot(100, nodes.AsReadOnly()); + + var arg = new FrostFsNodeInfo[1][]; + var pivot = "objectID"u8.ToArray(); + + arg[0] = [.. nodes]; + var result = netmap.PlacementVectors(arg, pivot); + + Assert.Single(result); + Assert.Equal(3, result[0].Length); + Assert.Equal(key1, result[0][0].PublicKey); + Assert.Equal(key2, result[0][1].PublicKey); + Assert.Equal(key3, result[0][2].PublicKey); + } + + [Fact] + public void TestPlacementPolicyUnique() + { + FrostFsVersion version = new(2, 13); + var p = new FrostFsPlacementPolicy(true, [new FrostFsReplica(1, "S"), new FrostFsReplica(1, "S")]) + { + BackupFactor = 2 + }; + p.Selectors.Add(new FrostFsSelector("S") + { + Attribute = "City", + Count = 1, + Filter = "*", + Clause = (int)FrostFsClause.Same + }); + + List nodes = []; + + var cities = new string[] { "Moscow", "Berlin", "Shenzhen" }; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + var attr = new Dictionary { { "City", cities[i] } }; + var key = new byte[] { (byte)(i * 4 + j) }; + var node = new FrostFsNodeInfo(version, NodeState.Online, [], attr, key); + + nodes.Add(node); + } + } + + var netMap = new FrostFsNetmapSnapshot(100, nodes.AsReadOnly()); + + var v = netMap.ContainerNodes(p, null); + + Assert.Equal(2, v.Length); + Assert.Equal(2, v[0].Length); + Assert.Equal(2, v[1].Length); + + + for (int i = 0; i < v.Length; i++) + { + foreach (var ni in v[i]) + { + for (int j = 0; j < i; j++) + { + foreach (var nj in v[j]) + { + Assert.NotEqual(ni.Hash, nj.Hash); + } + } + } + } + } +} From 43e300c7730f7545f87f168a5f82cc15f8f96218 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 13 Jan 2025 10:34:44 +0300 Subject: [PATCH 39/65] [#29] Client: Add PlacementVector unit tests Signed-off-by: Pavel Gross --- .../Mappers/Netmap/PlacementPolicy.cs | 3 + .../Mappers/Netmap/Replica.cs | 66 ++- .../Models/Netmap/FrostFsNetmapSnapshot.cs | 41 +- .../Models/Netmap/FrostFsPlacementPolicy.cs | 16 +- .../Models/Netmap/FrostFsSelector.cs | 4 +- .../Models/Netmap/Placement/Context.cs | 73 ++-- .../Models/Netmap/Placement/HasherList.cs | 8 + .../Models/Netmap/Placement/MeanAgg.cs | 2 +- .../Models/Netmap/Placement/Tools.cs | 24 +- .../ContainerServiceBase.cs | 2 +- .../Multithread/MultithreadPoolSmokeTests.cs | 10 +- .../MultithreadSmokeClientTests.cs | 16 +- src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs | 10 +- .../Smoke/SmokeClientTests.cs | 16 +- .../TestData/PlacementTests/cbf_default.json | 100 +++++ .../TestData/PlacementTests/cbf_minimal.json | 101 +++++ .../PlacementTests/cbf_requirements.json | 156 +++++++ .../PlacementTests/filter_complex.json | 345 +++++++++++++++ .../filter_invalid_integer.json | 81 ++++ .../PlacementTests/filter_simple.json | 397 ++++++++++++++++++ .../TestData/PlacementTests/hrw_sort.json | 225 ++++++++++ .../TestData/PlacementTests/issue213.json | 107 +++++ .../TestData/PlacementTests/many_selects.json | 279 ++++++++++++ .../TestData/PlacementTests/multiple_rep.json | 93 ++++ .../multiple_rep_asymmetric.json | 328 +++++++++++++++ .../TestData/PlacementTests/non_strict.json | 97 +++++ .../TestData/PlacementTests/rep_only.json | 113 +++++ .../PlacementTests/select_no_attribute.json | 116 +++++ .../PlacementTests/selector_invalid.json | 87 ++++ .../Unit/ContainerTestsBase.cs | 2 +- src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs | 2 +- .../Unit/PlacementVectorTests.cs | 366 ++++++++++------ .../Unit/SessionTestsBase.cs | 2 +- 33 files changed, 3054 insertions(+), 234 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_default.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_minimal.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_requirements.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_complex.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_invalid_integer.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_simple.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/hrw_sort.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/issue213.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/many_selects.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep_asymmetric.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/non_strict.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/rep_only.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/select_no_attribute.json create mode 100644 src/FrostFS.SDK.Tests/TestData/PlacementTests/selector_invalid.json diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs index 5f1600c..30410aa 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs @@ -16,6 +16,9 @@ public static class PlacementPolicyMapper return new FrostFsPlacementPolicy( placementPolicy.Unique, + placementPolicy.ContainerBackupFactor, + new System.Collections.ObjectModel.Collection(placementPolicy.Selectors.Select(selector => selector.ToModel()).ToList()), + new System.Collections.ObjectModel.Collection(placementPolicy.Filters.Select(filter => filter.ToModel()).ToList()), placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() ); } diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs index f0ace94..bc415e1 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs @@ -1,10 +1,11 @@ using System; +using System.Linq; using FrostFS.Netmap; namespace FrostFS.SDK.Client; -public static class ReplicaMapper +public static class PolicyMapper { public static Replica ToMessage(this FrostFsReplica replica) { @@ -24,4 +25,67 @@ public static class ReplicaMapper return new FrostFsReplica((int)replica.Count, replica.Selector); } + + public static Selector ToMessage(this FrostFsSelector selector) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new Selector + { + Name = selector.Name, + Count = selector.Count, + Clause = (Clause)selector.Clause, + Attribute = selector.Attribute, + Filter = selector.Filter + }; + } + + public static FrostFsSelector ToModel(this Selector selector) + { + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return new FrostFsSelector(selector.Name) + { + Count = selector.Count, + Clause = (int)selector.Clause, + Attribute = selector.Attribute, + Filter = selector.Filter + }; + } + + public static Filter ToMessage(this FrostFsFilter filter) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + var message = new Filter + { + Name = filter.Name, + Key = filter.Key, + Op = (Operation)filter.Operation, + Value = filter.Value, + }; + + message.Filters.AddRange(filter.Filters.Select(f => f.ToMessage())); + + return message; + } + + public static FrostFsFilter ToModel(this Filter filter) + { + if (filter is null) + { + throw new ArgumentNullException(nameof(filter)); + } + + return new FrostFsFilter(filter.Name, filter.Key, (int)filter.Op, filter.Value, filter.Filters.Select(f => f.ToModel()).ToArray()); + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs index 686c966..1d90b04 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using FrostFS.SDK.Client; @@ -58,9 +57,9 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n result[i][j] = vectors[i][j]; } - Tools.AppendWeightsTo(result[i], wf, spanWeigths); + Tools.AppendWeightsTo(result[i], wf, ref spanWeigths); - Tools.SortHasherSliceByWeightValue(result[i].ToList(), spanWeigths, hash); + result[i] = Tools.SortHasherSliceByWeightValue(result[i].ToList(), spanWeigths, hash).ToArray(); } return result; @@ -74,12 +73,7 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n // of the last select application. List> SelectFilterNodes(SelectFilterExpr expr) { - var policy = new FrostFsPlacementPolicy(false); - - foreach (var f in expr.Filters) - policy.Filters.Add(f); - - policy.Selectors.Add(expr.Selector); + var policy = new FrostFsPlacementPolicy(false, expr.Cbf, [expr.Selector], expr.Filters); var ctx = new Context(this) { @@ -166,7 +160,7 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n { var c = new Context(this) { - Cbf = p.BackupFactor + Cbf = p.BackupFactor == 0 ? 3 : p.BackupFactor }; if (pivot != null && pivot.Length > 0) @@ -198,20 +192,14 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n if (string.IsNullOrEmpty(sName) && !(p.Replicas.Length == 1 && p.Selectors.Count == 1)) { - var s = new FrostFsSelector(Context.mainFilterName) + var s = new FrostFsSelector(string.Empty) { - Count = p.Replicas[i].CountNodes() + Count = p.Replicas[i].CountNodes(), + Filter = Context.mainFilterName }; var nodes = c.GetSelection(s); - - var arg = new List>(nodes.Count); - for (int j = 0; j < nodes.Count; j++) - { - arg[i] = nodes[j]; - } - - result[i].AddRange(FlattenNodes(arg)); + result[i].AddRange(FlattenNodes(nodes)); if (unique) { @@ -243,23 +231,16 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n else { var nodes = c.Selections[sName]; - - var arg = new List>(nodes.Count); - for (int j = 0; j < nodes.Count; j++) - { - arg[i] = nodes[j]; - } - - result[i].AddRange(FlattenNodes(arg)); + result[i].AddRange(FlattenNodes(nodes)); } } var collection = new FrostFsNodeInfo[result.Count][]; - for(int i =0; i < result.Count; i++) + for (int i = 0; i < result.Count; i++) { collection[i] = [.. result[i]]; } - + return collection; } } diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index 19fe755..6ca8ab9 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -7,20 +7,24 @@ using FrostFS.SDK.Client; namespace FrostFS.SDK; -public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) +public struct FrostFsPlacementPolicy(bool unique, + uint backupFactor, + Collection selectors, + Collection filters, + params FrostFsReplica[] replicas) : IEquatable { private PlacementPolicy policy; - public FrostFsReplica[] Replicas { get; private set; } = replicas; + public FrostFsReplica[] Replicas { get; } = replicas; - public Collection Selectors { get; } = []; + public Collection Selectors { get; } = selectors; - public Collection Filters { get; } = []; + public Collection Filters { get; } = filters; - public bool Unique { get; private set; } = unique; + public bool Unique { get; } = unique; - public uint BackupFactor { get; set; } + public uint BackupFactor { get; } = backupFactor; public override readonly bool Equals(object obj) { diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs index 74380f8..3369d6c 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs @@ -2,9 +2,9 @@ public class FrostFsSelector(string name) { - public string Name { get; } = name; + public string Name { get; set; } = name; public uint Count { get; set; } - public uint Clause { get; set; } + public int Clause { get; set; } public string? Attribute { get; set; } public string? Filter { get; set; } } diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs index fc6e80b..ed4ad82 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs @@ -104,20 +104,23 @@ internal struct Context return; } - if (filter.Operation == (int)Operation.EQ || - filter.Operation == (int)Operation.NE || - filter.Operation == (int)Operation.LIKE || - filter.Operation == (int)Operation.GT || - filter.Operation == (int)Operation.GE || - filter.Operation == (int)Operation.LT || - filter.Operation == (int)Operation.LE) + switch (filter.Operation) { - var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture); - NumCache[filter.Value] = n; - } - else - { - throw new FrostFsException($"{errInvalidFilterOp}: {filter.Operation}"); + case (int)Operation.EQ: + case (int)Operation.NE: + case (int)Operation.LIKE: + break; + case (int)Operation.GT: + case (int)Operation.GE: + case (int)Operation.LT: + case (int)Operation.LE: + { + var n = uint.Parse(filter.Value, CultureInfo.InvariantCulture); + NumCache[filter.Value] = n; + break; + } + default: + throw new FrostFsException($"{errInvalidFilterOp}: {filter.Operation}"); } } @@ -153,7 +156,7 @@ internal struct Context // for the given selector. static (int bucketCount, int nodesInBucket) CalcNodesCount(FrostFsSelector selector) { - return selector.Clause == (uint)FrostFsClause.Same + return selector.Clause == (int)FrostFsClause.Same ? (1, (int)selector.Count) : ((int)selector.Count, 1); } @@ -211,11 +214,17 @@ internal struct Context { double[] ws = []; - foreach (var res in result) + var sortedNodes = new NodeAttrPair[result.Count]; + + for (int i = 0; i < result.Count; i++) { - Tools.AppendWeightsTo(res.nodes, weightFunc, ws); - Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash); + var res = result[i]; + Tools.AppendWeightsTo(res.nodes, weightFunc, ref ws); + sortedNodes[i].nodes = Tools.SortHasherSliceByWeightValue(res.nodes.ToList(), ws, HrwSeedHash).ToArray(); + sortedNodes[i].attr = result[i].attr; } + + return sortedNodes; } return [.. result]; } @@ -273,7 +282,7 @@ internal struct Context if (res.Count < bucketCount) { // Fallback to using minimum allowed backup factor (1). - res = fallback; + res.AddRange(fallback); if (Strict && res.Count < bucketCount) { @@ -293,7 +302,12 @@ internal struct Context } var hashers = res.Select(r => new HasherList(r)).ToList(); - Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash); + hashers = Tools.SortHasherSliceByWeightValue(hashers, weights, HrwSeedHash); + + for (int i = 0; i < res.Count; i++) + { + res[i] = hashers[i].Nodes; + } } if (res.Count < bucketCount) @@ -331,7 +345,7 @@ internal struct Context switch (f.Operation) { case (int)Operation.EQ: - return nodeInfo.Attributes[f.Key] == f.Value; + return nodeInfo.Attributes.TryGetValue(f.Key, out var val) && val == f.Value; case (int)Operation.LIKE: { var hasPrefix = f.Value.StartsWith(likeWildcard, StringComparison.Ordinal); @@ -357,12 +371,21 @@ internal struct Context return nodeInfo.Attributes[f.Key] != f.Value; default: { - var attr = f.Key switch + ulong attr; + switch (f.Key) { - FrostFsNodeInfo.AttrPrice => nodeInfo.Price, - FrostFsNodeInfo.AttrCapacity => nodeInfo.GetCapacity(), - _ => uint.Parse(nodeInfo.Attributes[f.Key], CultureInfo.InvariantCulture), - }; + case FrostFsNodeInfo.AttrPrice: + attr = nodeInfo.Price; + break; + + case FrostFsNodeInfo.AttrCapacity: + attr = nodeInfo.GetCapacity(); + break; + default: + if (!ulong.TryParse(nodeInfo.Attributes[f.Key], NumberStyles.Integer, CultureInfo.InvariantCulture, out attr)) + return false; + break; + } switch (f.Operation) { diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs index 117980c..17fabfe 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/HasherList.cs @@ -11,6 +11,14 @@ internal sealed class HasherList : IHasher _nodes = nodes; } + internal List Nodes + { + get + { + return _nodes; + } + } + public ulong Hash() { return _nodes.Count > 0 ? _nodes[0].Hash() : 0; diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs index 0febba1..98187b3 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/MeanAgg.cs @@ -8,7 +8,7 @@ internal struct MeanAgg internal void Add(double n) { int c = count + 1; - mean *= (double)count / c + n / c; + mean = mean * count / c + n / c; count++; } diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs index b6de113..e87994a 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Tools.cs @@ -36,7 +36,7 @@ public static class Tools return x / (1 + x); } - internal static void AppendWeightsTo(FrostFsNodeInfo[] nodes, Func wf, double[] weights) + internal static void AppendWeightsTo(FrostFsNodeInfo[] nodes, Func wf, ref double[] weights) { if (weights.Length < nodes.Length) { @@ -49,11 +49,11 @@ public static class Tools } } - internal static void SortHasherSliceByWeightValue(List nodes, Span weights, ulong hash) where T : IHasher + internal static List SortHasherSliceByWeightValue(List nodes, Span weights, ulong hash) where T : IHasher { if (nodes.Count == 0) { - return; + return nodes; } var allEquals = true; @@ -70,7 +70,7 @@ public static class Tools } } - var dist = new ulong[nodes.Count]; + var dist = new double[nodes.Count]; if (allEquals) { @@ -80,20 +80,19 @@ public static class Tools dist[i] = Distance(x, hash); } - SortHasherByDistance(nodes, dist, true); - return; + return SortHasherByDistance(nodes, dist, true); } for (int i = 0; i < dist.Length; i++) { var d = Distance(nodes[i].Hash(), hash); - dist[i] = ulong.MaxValue - (ulong)(d * weights[i]); + dist[i] = (ulong.MaxValue - d) * weights[i]; } - SortHasherByDistance(nodes, dist, false); + return SortHasherByDistance(nodes, dist, false); } - internal static void SortHasherByDistance(List nodes, N[] dist, bool asc) + internal static List SortHasherByDistance(List nodes, N[] dist, bool asc) { IndexedValue[] indexes = new IndexedValue[nodes.Count]; for (int i = 0; i < dist.Length; i++) @@ -103,16 +102,15 @@ public static class Tools if (asc) { - nodes = new List(indexes + return new List(indexes .OrderBy(x => x.dist) .Select(x => x.nodeInfo).ToArray()); } else { - nodes = new List(indexes + return new List(indexes .OrderByDescending(x => x.dist) - .Select(x => x.nodeInfo) - .ToArray()); + .Select(x => x.nodeInfo)); } } diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index 8d4230c..ab52c19 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -23,7 +23,7 @@ public abstract class ServiceBase(string key) public FrostFsPlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; public static FrostFsVersion DefaultVersion { get; } = new(2, 13); - public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); + public static FrostFsPlacementPolicy DefaultPlacementPolicy { get; } = new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)); #pragma warning disable CA2227 // this is specific object, should be treated as is public Metadata? Metadata { get; set; } diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs index 0cd3d6b..4cb7e4d 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs @@ -166,7 +166,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); @@ -216,7 +216,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.CreateContainerAsync(createContainerParam, default); @@ -311,7 +311,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -396,7 +396,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); var container = await pool.CreateContainerAsync(createContainerParam, ctx); @@ -479,7 +479,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.CreateContainerAsync(createContainerParam, default); diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs index e72e657..b239e58 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs @@ -99,7 +99,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); @@ -144,7 +144,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); @@ -237,7 +237,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -308,7 +308,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -388,7 +388,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -442,7 +442,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -499,7 +499,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); var container = await client.CreateContainerAsync(createContainerParam, ctx); @@ -580,7 +580,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs index ce626d5..94b2c8b 100644 --- a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs @@ -164,7 +164,7 @@ public class PoolSmokeTests : SmokeTestsBase var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); @@ -214,7 +214,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.CreateContainerAsync(createContainerParam, default); @@ -311,7 +311,7 @@ public class PoolSmokeTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.Zero); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -396,7 +396,7 @@ public class PoolSmokeTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); var container = await pool.CreateContainerAsync(createContainerParam, ctx); @@ -480,7 +480,7 @@ public class PoolSmokeTests : SmokeTestsBase await Cleanup(pool); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await pool.CreateContainerAsync(createContainerParam, default); diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs index db43eef..d774b47 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs @@ -81,7 +81,7 @@ public class SmokeClientTests : SmokeTestsBase var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); @@ -126,7 +126,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); @@ -219,7 +219,7 @@ public class SmokeClientTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -300,7 +300,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -380,7 +380,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -436,7 +436,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -493,7 +493,7 @@ public class SmokeClientTests : SmokeTestsBase var ctx = new CallContext(TimeSpan.FromSeconds(20)); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); var container = await client.CreateContainerAsync(createContainerParam, ctx); @@ -573,7 +573,7 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, new FrostFsReplica(1))), + new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); var containerId = await client.CreateContainerAsync(createContainerParam, default); diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_default.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_default.json new file mode 100644 index 0000000..25675f2 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_default.json @@ -0,0 +1,100 @@ +{ + "name": "default CBF is 3", + "nodes": [ + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "Moscow" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "DE" + }, + { + "key": "City", + "value": "Berlin" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "FR" + }, + { + "key": "City", + "value": "Paris" + } + ] + } + ], + "tests": [ + { + "name": "set default CBF", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "EU" + } + ], + "containerBackupFactor": 0, + "selectors": [ + { + "name": "EU", + "count": 1, + "clause": "SAME", + "attribute": "Location", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_minimal.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_minimal.json new file mode 100644 index 0000000..7553dd6 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_minimal.json @@ -0,0 +1,101 @@ +{ + "name": "Real node count multiplier is in range [1, specified CBF]", + "nodes": [ + { + "attributes": [ + { + "key": "ID", + "value": "1" + }, + { + "key": "Country", + "value": "DE" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "2" + }, + { + "key": "Country", + "value": "DE" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "3" + }, + { + "key": "Country", + "value": "DE" + } + ] + } + ], + "tests": [ + { + "name": "select 2, CBF is 2", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "X" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "X", + "count": 2, + "clause": "SAME", + "attribute": "Country", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2 + ] + ] + }, + { + "name": "select 3, CBF is 2", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "X" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "X", + "count": 3, + "clause": "SAME", + "attribute": "Country", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_requirements.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_requirements.json new file mode 100644 index 0000000..279d919 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/cbf_requirements.json @@ -0,0 +1,156 @@ +{ + "name": "CBF requirements", + "nodes": [ + { + "attributes": [ + { + "key": "ID", + "value": "1" + }, + { + "key": "Attr", + "value": "Same" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "2" + }, + { + "key": "Attr", + "value": "Same" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "3" + }, + { + "key": "Attr", + "value": "Same" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "4" + }, + { + "key": "Attr", + "value": "Same" + } + ] + } + ], + "tests": [ + { + "name": "default CBF, no selector", + "policy": { + "replicas": [ + { + "count": 2 + } + ], + "containerBackupFactor": 0, + "selectors": [], + "filters": [] + }, + "result": [ + [ + 0, + 2, + 1, + 3 + ] + ] + }, + { + "name": "explicit CBF, no selector", + "policy": { + "replicas": [ + { + "count": 2 + } + ], + "containerBackupFactor": 3, + "selectors": [], + "filters": [] + }, + "result": [ + [ + 0, + 2, + 1, + 3 + ] + ] + }, + { + "name": "select distinct, weak CBF", + "policy": { + "replicas": [ + { + "count": 2, + "selector": "X" + } + ], + "containerBackupFactor": 3, + "selectors": [ + { + "name": "X", + "count": 2, + "clause": "DISTINCT", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 2, + 1, + 3 + ] + ] + }, + { + "name": "select same, weak CBF", + "policy": { + "replicas": [ + { + "count": 2, + "selector": "X" + } + ], + "containerBackupFactor": 3, + "selectors": [ + { + "name": "X", + "count": 2, + "clause": "SAME", + "attribute": "Attr", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_complex.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_complex.json new file mode 100644 index 0000000..c2c19c0 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_complex.json @@ -0,0 +1,345 @@ +{ + "name": "compound filter", + "nodes": [ + { + "attributes": [ + { + "key": "Storage", + "value": "SSD" + }, + { + "key": "Rating", + "value": "10" + }, + { + "key": "IntField", + "value": "100" + }, + { + "key": "Param", + "value": "Value1" + } + ] + } + ], + "tests": [ + { + "name": "good", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "StorageSSD", + "key": "Storage", + "op": "EQ", + "value": "SSD", + "filters": [] + }, + { + "name": "GoodRating", + "key": "Rating", + "op": "GE", + "value": "4", + "filters": [] + }, + { + "name": "Main", + "op": "AND", + "filters": [ + { + "name": "StorageSSD", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "key": "IntField", + "op": "LT", + "value": "123", + "filters": [] + }, + { + "name": "GoodRating", + "op": "Unspecified", + "filters": [] + }, + { + "op": "OR", + "filters": [ + { + "key": "Param", + "op": "EQ", + "value": "Value1", + "filters": [] + }, + { + "key": "Param", + "op": "EQ", + "value": "Value2", + "filters": [] + } + ] + } + ] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "bad storage type", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "StorageSSD", + "key": "Storage", + "op": "EQ", + "value": "HDD", + "filters": [] + }, + { + "name": "GoodRating", + "key": "Rating", + "op": "GE", + "value": "4", + "filters": [] + }, + { + "name": "Main", + "op": "AND", + "filters": [ + { + "name": "StorageSSD", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "key": "IntField", + "op": "LT", + "value": "123", + "filters": [] + }, + { + "name": "GoodRating", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "op": "OR", + "filters": [ + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value1", + "filters": [] + }, + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value2", + "filters": [] + } + ] + } + ] + } + ] + } + }, + { + "name": "bad rating", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "StorageSSD", + "key": "Storage", + "op": "EQ", + "value": "SSD", + "filters": [] + }, + { + "name": "GoodRating", + "key": "Rating", + "op": "GE", + "value": "15", + "filters": [] + }, + { + "name": "Main", + "op": "AND", + "filters": [ + { + "name": "StorageSSD", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "key": "IntField", + "op": "LT", + "value": "123", + "filters": [] + }, + { + "name": "GoodRating", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "op": "OR", + "filters": [ + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value1", + "filters": [] + }, + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value2", + "filters": [] + } + ] + } + ] + } + ] + } + }, + { + "name": "bad param", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "StorageSSD", + "key": "Storage", + "op": "EQ", + "value": "SSD", + "filters": [] + }, + { + "name": "GoodRating", + "key": "Rating", + "op": "GE", + "value": "4", + "filters": [] + }, + { + "name": "Main", + "op": "AND", + "filters": [ + { + "name": "StorageSSD", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "key": "IntField", + "op": "LT", + "value": "123", + "filters": [] + }, + { + "name": "GoodRating", + "op": "Unspecified", + "filters": [] + }, + { + "name": "", + "op": "OR", + "filters": [ + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value0", + "filters": [] + }, + { + "name": "", + "key": "Param", + "op": "EQ", + "value": "Value2", + "filters": [] + } + ] + } + ] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_invalid_integer.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_invalid_integer.json new file mode 100644 index 0000000..16cdb9e --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_invalid_integer.json @@ -0,0 +1,81 @@ +{ + "name": "invalid integer field", + "nodes": [ + { + "attributes": [ + { + "key": "IntegerField", + "value": "true" + } + ] + }, + { + "attributes": [ + { + "key": "IntegerField", + "value": "str" + } + ] + } + ], + "tests": [ + { + "name": "empty string is not casted to 0", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "IntegerField", + "op": "LE", + "value": "8", + "filters": [] + } + ] + } + }, + { + "name": "non-empty string is not casted to a number", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "IntegerField", + "op": "GE", + "value": "0", + "filters": [] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_simple.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_simple.json new file mode 100644 index 0000000..43142d4 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/filter_simple.json @@ -0,0 +1,397 @@ +{ + "name": "single-op filters", + "nodes": [ + { + "attributes": [ + { + "key": "Rating", + "value": "4" + }, + { + "key": "Country", + "value": "Germany" + } + ] + } + ], + "tests": [ + { + "name": "GE true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "GE", + "value": "4", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "GE false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "GE", + "value": "5", + "filters": [] + } + ] + } + }, + { + "name": "GT true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "GT", + "value": "3", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "GT false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "GT", + "value": "4", + "filters": [] + } + ] + } + }, + { + "name": "LE true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "LE", + "value": "4", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "LE false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "LE", + "value": "3", + "filters": [] + } + ] + } + }, + { + "name": "LT true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "LT", + "value": "5", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "LT false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Rating", + "op": "LT", + "value": "4", + "filters": [] + } + ] + } + }, + { + "name": "EQ true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Country", + "op": "EQ", + "value": "Germany", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "EQ false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Country", + "op": "EQ", + "value": "China", + "filters": [] + } + ] + } + }, + { + "name": "NE true", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Country", + "op": "NE", + "value": "France", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "NE false", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "S" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "S", + "count": 1, + "clause": "DISTINCT", + "filter": "Main" + } + ], + "filters": [ + { + "name": "Main", + "key": "Country", + "op": "NE", + "value": "Germany", + "filters": [] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/hrw_sort.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/hrw_sort.json new file mode 100644 index 0000000..a6dc75c --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/hrw_sort.json @@ -0,0 +1,225 @@ +{ + "name": "HRW ordering", + "nodes": [ + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + }, + { + "key": "Price", + "value": "2" + }, + { + "key": "Capacity", + "value": "10000" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + }, + { + "key": "Price", + "value": "4" + }, + { + "key": "Capacity", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "France" + }, + { + "key": "Price", + "value": "3" + }, + { + "key": "Capacity", + "value": "10" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Price", + "value": "2" + }, + { + "key": "Capacity", + "value": "10000" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Price", + "value": "1" + }, + { + "key": "Capacity", + "value": "10000" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Capacity", + "value": "10000" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "France" + }, + { + "key": "Price", + "value": "100" + }, + { + "key": "Capacity", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "France" + }, + { + "key": "Price", + "value": "7" + }, + { + "key": "Capacity", + "value": "10000" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Price", + "value": "2" + }, + { + "key": "Capacity", + "value": "1" + } + ] + } + ], + "tests": [ + { + "name":"select 3 nodes in 3 distinct countries, same placement", + "policy": { + "containerBackupFactor": 1, + "filters": [], + "replicas": [ + { + "count": 1, + "selector": "Main" + } + ], + "selectors": [ + { + "attribute": "Country", + "clause": "DISTINCT", + "count": 3, + "filter": "*", + "name": "Main" + } + ] + }, + "pivot": "Y29udGFpbmVySUQ=", + "result": [[ + 5, + 0, + 7 + ]], + "placement": { + "pivot": "b2JqZWN0SUQ=", + "result": [[ + 5, + 0, + 7 + ]] + } + }, + { + "name":"select 6 nodes in 3 distinct countries, different placement", + "policy": { + "containerBackupFactor": 2, + "filters": [ + ], + "replicas": [ + { + "count": 1, + "selector": "Main" + } + ], + "selectors": [ + { + "attribute": "Country", + "clause": "DISTINCT", + "count": 3, + "filter": "*", + "name": "Main" + } + ] + }, + "pivot": "Y29udGFpbmVySUQ=", + "result": [[ + 5, + 4, + 0, + 1, + 7, + 2]], + + "placement": { + "pivot": "b2JqZWN0SUQ=", + "result": [[ + 5, + 4, + 0, + 7, + 2, + 1]] + } + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/issue213.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/issue213.json new file mode 100644 index 0000000..4430297 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/issue213.json @@ -0,0 +1,107 @@ +{ + "name": "unnamed selector (nspcc-dev/neofs-api-go#213)", + "nodes": [ + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "Russia" + }, + { + "key": "City", + "value": "Moscow" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "Russia" + }, + { + "key": "City", + "value": "Saint-Petersburg" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "Sweden" + }, + { + "key": "City", + "value": "Stockholm" + } + ] + }, + { + "attributes": [ + { + "key": "Location", + "value": "Europe" + }, + { + "key": "Country", + "value": "Finalnd" + }, + { + "key": "City", + "value": "Helsinki" + } + ] + } + ], + "tests": [ + { + "name": "test", + "policy": { + "replicas": [ + { + "count": 4 + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "", + "count": 4, + "clause": "DISTINCT", + "filter": "LOC_EU" + } + ], + "filters": [ + { + "name": "LOC_EU", + "key": "Location", + "op": "EQ", + "value": "Europe", + "filters": [] + } + ] + }, + "result": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/many_selects.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/many_selects.json new file mode 100644 index 0000000..e76a441 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/many_selects.json @@ -0,0 +1,279 @@ +{ + "name": "single-op filters", + "nodes": [ + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "1" + }, + { + "key": "City", + "value": "SPB" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + }, + { + "key": "Rating", + "value": "5" + }, + { + "key": "City", + "value": "Berlin" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "6" + }, + { + "key": "City", + "value": "Moscow" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "France" + }, + { + "key": "Rating", + "value": "4" + }, + { + "key": "City", + "value": "Paris" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "France" + }, + { + "key": "Rating", + "value": "1" + }, + { + "key": "City", + "value": "Lyon" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "5" + }, + { + "key": "City", + "value": "SPB" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "7" + }, + { + "key": "City", + "value": "Moscow" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + }, + { + "key": "Rating", + "value": "3" + }, + { + "key": "City", + "value": "Darmstadt" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + }, + { + "key": "Rating", + "value": "7" + }, + { + "key": "City", + "value": "Frankfurt" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "9" + }, + { + "key": "City", + "value": "SPB" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + }, + { + "key": "Rating", + "value": "9" + }, + { + "key": "City", + "value": "SPB" + } + ] + } + ], + "tests": [ + { + "name": "Select", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "SameRU" + }, + { + "count": 1, + "selector": "DistinctRU" + }, + { + "count": 1, + "selector": "Good" + }, + { + "count": 1, + "selector": "Main" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "SameRU", + "count": 2, + "clause": "SAME", + "attribute": "City", + "filter": "FromRU" + }, + { + "name": "DistinctRU", + "count": 2, + "clause": "DISTINCT", + "attribute": "City", + "filter": "FromRU" + }, + { + "name": "Good", + "count": 2, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "Good" + }, + { + "name": "Main", + "count": 3, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "*" + } + ], + "filters": [ + { + "name": "FromRU", + "key": "Country", + "op": "EQ", + "value": "Russia" + }, + { + "name": "Good", + "key": "Rating", + "op": "GE", + "value": "4" + } + ] + }, + "result": [ + [ + 0, + 5, + 9, + 10 + ], + [ + 2, + 6, + 0, + 5 + ], + [ + 1, + 8, + 2, + 5 + ], + [ + 3, + 4, + 1, + 7, + 0, + 2 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep.json new file mode 100644 index 0000000..e01ad62 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep.json @@ -0,0 +1,93 @@ +{ + "name": "multiple replicas (#215)", + "nodes": [ + { + "attributes": [ + { + "key": "City", + "value": "Saint-Petersburg" + } + ] + }, + { + "attributes": [ + { + "key": "City", + "value": "Moscow" + } + ] + }, + { + "attributes": [ + { + "key": "City", + "value": "Berlin" + } + ] + }, + { + "attributes": [ + { + "key": "City", + "value": "Paris" + } + ] + } + ], + "tests": [ + { + "name": "test", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "LOC_SPB_PLACE" + }, + { + "count": 1, + "selector": "LOC_MSK_PLACE" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "LOC_SPB_PLACE", + "count": 1, + "clause": "UNSPECIFIED", + "filter": "LOC_SPB" + }, + { + "name": "LOC_MSK_PLACE", + "count": 1, + "clause": "UNSPECIFIED", + "filter": "LOC_MSK" + } + ], + "filters": [ + { + "name": "LOC_SPB", + "key": "City", + "op": "EQ", + "value": "Saint-Petersburg", + "filters": [] + }, + { + "name": "LOC_MSK", + "key": "City", + "op": "EQ", + "value": "Moscow", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ], + [ + 1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep_asymmetric.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep_asymmetric.json new file mode 100644 index 0000000..55390b2 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/multiple_rep_asymmetric.json @@ -0,0 +1,328 @@ +{ + "name": "multiple REP, asymmetric", + "nodes": [ + { + "attributes": [ + { + "key": "ID", + "value": "1" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + }, + { + "key": "SSD", + "value": "0" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "2" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "3" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "Moscow" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "4" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "Moscow" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "5" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "6" + }, + { + "key": "Continent", + "value": "NA" + }, + { + "key": "City", + "value": "NewYork" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "7" + }, + { + "key": "Continent", + "value": "AF" + }, + { + "key": "City", + "value": "Cairo" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "8" + }, + { + "key": "Continent", + "value": "AF" + }, + { + "key": "City", + "value": "Cairo" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "9" + }, + { + "key": "Continent", + "value": "SA" + }, + { + "key": "City", + "value": "Lima" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "10" + }, + { + "key": "Continent", + "value": "AF" + }, + { + "key": "City", + "value": "Cairo" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "11" + }, + { + "key": "Continent", + "value": "NA" + }, + { + "key": "City", + "value": "NewYork" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "12" + }, + { + "key": "Continent", + "value": "NA" + }, + { + "key": "City", + "value": "LosAngeles" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "13" + }, + { + "key": "Continent", + "value": "SA" + }, + { + "key": "City", + "value": "Lima" + } + ] + } + ], + "tests": [ + { + "name": "test", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "SPB" + }, + { + "count": 2, + "selector": "Americas" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "SPB", + "count": 1, + "clause": "SAME", + "attribute": "City", + "filter": "SPBSSD" + }, + { + "name": "Americas", + "count": 2, + "clause": "DISTINCT", + "attribute": "City", + "filter": "Americas" + } + ], + "filters": [ + { + "name": "SPBSSD", + "op": "AND", + "filters": [ + { + "name": "", + "key": "Country", + "op": "EQ", + "value": "RU", + "filters": [] + }, + { + "name": "", + "key": "City", + "op": "EQ", + "value": "St.Petersburg", + "filters": [] + }, + { + "name": "", + "key": "SSD", + "op": "EQ", + "value": "1", + "filters": [] + } + ] + }, + { + "name": "Americas", + "op": "OR", + "filters": [ + { + "name": "", + "key": "Continent", + "op": "EQ", + "value": "NA", + "filters": [] + }, + { + "name": "", + "key": "Continent", + "op": "EQ", + "value": "SA", + "filters": [] + } + ] + } + ] + }, + "result": [ + [ + 1, + 4 + ], + [ + 8, + 12, + 5, + 10 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/non_strict.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/non_strict.json new file mode 100644 index 0000000..d8e010e --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/non_strict.json @@ -0,0 +1,97 @@ +{ + "name": "non-strict selections", + "comment": "These test specify loose selection behaviour, to allow fetching already PUT objects even when there is not enough nodes to select from.", + "nodes": [ + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + } + ] + }, + { + "attributes": [] + } + ], + "tests": [ + { + "name": "not enough nodes (backup factor)", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "MyStore" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "MyStore", + "count": 2, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "FromRU" + } + ], + "filters": [ + { + "name": "FromRU", + "key": "Country", + "op": "EQ", + "value": "Russia", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + }, + { + "name": "not enough nodes (buckets)", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "MyStore" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "MyStore", + "count": 2, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "FromRU" + } + ], + "filters": [ + { + "name": "FromRU", + "key": "Country", + "op": "EQ", + "value": "Russia", + "filters": [] + } + ] + }, + "result": [ + [ + 0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/rep_only.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/rep_only.json new file mode 100644 index 0000000..7af5eb0 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/rep_only.json @@ -0,0 +1,113 @@ +{ + "name": "REP X", + "nodes": [ + { + "publicKey": "", + "addresses": [], + "attributes": [ + { + "key": "City", + "value": "Saint-Petersburg" + } + ], + "state": "Unspecified" + }, + { + "publicKey": "", + "addresses": [], + "attributes": [ + { + "key": "City", + "value": "Moscow" + } + ], + "state": "Unspecified" + }, + { + "publicKey": "", + "addresses": [], + "attributes": [ + { + "key": "City", + "value": "Berlin" + } + ], + "state": "Unspecified" + }, + { + "publicKey": "", + "addresses": [], + "attributes": [ + { + "key": "City", + "value": "Paris" + } + ], + "state": "Unspecified" + } + ], + "tests": [ + { + "name": "REP 1", + "policy": { + "replicas": [ + { + "count": 1 + } + ], + "containerBackupFactor": 0, + "selectors": [], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2 + ] + ] + }, + { + "name": "REP 3", + "policy": { + "replicas": [ + { + "count": 3 + } + ], + "containerBackupFactor": 0, + "selectors": [], + "filters": [] + }, + "result": [ + [ + 0, + 3, + 1, + 2 + ] + ] + }, + { + "name": "REP 5", + "policy": { + "replicas": [ + { + "count": 5 + } + ], + "containerBackupFactor": 0, + "selectors": [], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/select_no_attribute.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/select_no_attribute.json new file mode 100644 index 0000000..6a49d68 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/select_no_attribute.json @@ -0,0 +1,116 @@ +{ + "name": "select with unspecified attribute", + "nodes": [ + { + "attributes": [ + { + "key": "ID", + "value": "1" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + }, + { + "key": "SSD", + "value": "0" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "2" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "St.Petersburg" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "3" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "Moscow" + }, + { + "key": "SSD", + "value": "1" + } + ] + }, + { + "attributes": [ + { + "key": "ID", + "value": "4" + }, + { + "key": "Country", + "value": "RU" + }, + { + "key": "City", + "value": "Moscow" + }, + { + "key": "SSD", + "value": "1" + } + ] + } + ], + "tests": [ + { + "name": "test", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "X" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "X", + "count": 4, + "clause": "DISTINCT", + "filter": "*" + } + ], + "filters": [] + }, + "result": [ + [ + 0, + 1, + 2, + 3 + ] + ] + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/TestData/PlacementTests/selector_invalid.json b/src/FrostFS.SDK.Tests/TestData/PlacementTests/selector_invalid.json new file mode 100644 index 0000000..08e1a8d --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/PlacementTests/selector_invalid.json @@ -0,0 +1,87 @@ +{ + "name": "invalid selections", + "nodes": [ + { + "attributes": [ + { + "key": "Country", + "value": "Russia" + } + ] + }, + { + "attributes": [ + { + "key": "Country", + "value": "Germany" + } + ] + }, + { + "attributes": [] + } + ], + "tests": [ + { + "name": "missing filter", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "MyStore" + } + ], + "containerBackupFactor": 1, + "selectors": [ + { + "name": "MyStore", + "count": 1, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "FromNL" + } + ], + "filters": [ + { + "name": "FromRU", + "key": "Country", + "op": "EQ", + "value": "Russia", + "filters": [] + } + ] + }, + "error": "filter not found" + }, + { + "name": "not enough nodes (filter results in empty set)", + "policy": { + "replicas": [ + { + "count": 1, + "selector": "MyStore" + } + ], + "containerBackupFactor": 2, + "selectors": [ + { + "name": "MyStore", + "count": 2, + "clause": "DISTINCT", + "attribute": "Country", + "filter": "FromMoon" + } + ], + "filters": [ + { + "name": "FromMoon", + "key": "Country", + "op": "EQ", + "value": "Moon", + "filters": [] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs index b6cd61c..60579f6 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTestsBase.cs @@ -21,7 +21,7 @@ public abstract class ContainerTestsBase Mocker = new ContainerMocker(key) { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + PlacementPolicy = new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), Version = new FrostFsVersion(2, 13), ContainerGuid = Guid.NewGuid() }; diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs index cff9064..18e3981 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs @@ -30,7 +30,7 @@ public abstract class ObjectTestsBase Mocker = new ObjectMocker(key) { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + PlacementPolicy = new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), Version = new FrostFsVersion(2, 13), ContainerGuid = Guid.NewGuid() }; diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs index e578f90..98d3ee9 100644 --- a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs @@ -1,161 +1,275 @@ +using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Serialization; using FrostFS.SDK.Client.Models.Netmap.Placement; +using Xunit.Abstractions; + namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -public class PlacementVectorTests +public class PlacementVectorTests(ITestOutputHelper testOutputHelper) { - Dictionary[] attribs; - - public PlacementVectorTests() + private static readonly JsonSerializerOptions serializeOptions = new() { - var attribs1 = new Dictionary - { - {"Country", "Germany" }, - {"Price", "2" }, - {"Capacity", "10000"} - }; + PropertyNameCaseInsensitive = true + }; - var attribs2 = new Dictionary - { - {"Country", "Germany" }, - {"Price", "4" }, - {"Capacity", "1"} - }; - - var attribs3 = new Dictionary - { - {"Country", "France" }, - {"Price", "3" }, - {"Capacity", "10"} - }; - - var attribs4 = new Dictionary - { - {"Country", "Russia" }, - {"Price", "2" }, - {"Capacity", "10000"} - }; - - var attribs5 = new Dictionary - { - {"Country", "Russia" }, - {"Price", "1" }, - {"Capacity", "10000"} - }; - - var attribs6 = new Dictionary - { - {"Country", "Russia" }, - {"Capacity", "10000"} - }; - var attribs7 = new Dictionary - { - {"Country", "France" }, - {"Price", "100" }, - {"Capacity", "10000"} - }; - var attribs8 = new Dictionary - { - {"Country", "France" }, - {"Price", "7" }, - {"Capacity", "10000"} - }; - var attribs9 = new Dictionary - { - {"Country", "Russia" }, - {"Price", "2" }, - {"Capacity", "1"} - }; - - attribs = [attribs1, attribs2, attribs3, attribs4, attribs5, attribs6, attribs7, attribs8, attribs9]; - } + private readonly ITestOutputHelper _testOutputHelper = testOutputHelper; [Fact] - public void PlacementVectorTest() + public void PlacementTest() { + var path = ".\\..\\..\\..\\TestData\\PlacementTests"; + Assert.True(Directory.Exists(path)); + + var files = Directory.GetFiles(path); + FrostFsVersion v = new(2, 13); var addresses = new string[] { "localhost", "server1" }; - var key1 = new byte[] { 1 }; - var key2 = new byte[] { 2 }; - var key3 = new byte[] { 3 }; - var nodes = new List + foreach (var file in files.Where(f => f.EndsWith(".json", StringComparison.OrdinalIgnoreCase))) { - new(v, NodeState.Online, addresses.AsReadOnly(), attribs[5].AsReadOnly(), key1), - new(v, NodeState.Online, addresses.AsReadOnly(), attribs[0].AsReadOnly(), key2), - new(v, NodeState.Online, addresses.AsReadOnly(), attribs[8].AsReadOnly(), key3) - }; + //if (!file.EndsWith("selector_invalid.json")) + // continue; - var netmap = new FrostFsNetmapSnapshot(100, nodes.AsReadOnly()); + var fileName = file[(file.LastIndexOf("..\\") + 3)..]; + _testOutputHelper.WriteLine($"Open file {fileName}"); - var arg = new FrostFsNodeInfo[1][]; - var pivot = "objectID"u8.ToArray(); + var str = File.ReadAllText(file); + Assert.False(string.IsNullOrEmpty(str)); - arg[0] = [.. nodes]; - var result = netmap.PlacementVectors(arg, pivot); + var testCase = JsonSerializer.Deserialize(str, serializeOptions); - Assert.Single(result); - Assert.Equal(3, result[0].Length); - Assert.Equal(key1, result[0][0].PublicKey); - Assert.Equal(key2, result[0][1].PublicKey); - Assert.Equal(key3, result[0][2].PublicKey); - } + Assert.NotNull(testCase); + Assert.NotNull(testCase.Nodes); + Assert.True(testCase.Nodes.Length > 0); - [Fact] - public void TestPlacementPolicyUnique() - { - FrostFsVersion version = new(2, 13); - var p = new FrostFsPlacementPolicy(true, [new FrostFsReplica(1, "S"), new FrostFsReplica(1, "S")]) - { - BackupFactor = 2 - }; - p.Selectors.Add(new FrostFsSelector("S") - { - Attribute = "City", - Count = 1, - Filter = "*", - Clause = (int)FrostFsClause.Same - }); + _testOutputHelper.WriteLine($"Test case: \"{testCase.Name}\""); - List nodes = []; + var nodes = testCase.Nodes + .Select(n => new FrostFsNodeInfo(v, + n.State, + addresses.AsReadOnly(), + n.Attributes?.ToDictionary(x => x.Key, x => x.Value) ?? [], + n.PublicKeyBytes + ) + ) + .ToArray() + .AsReadOnly(); - var cities = new string[] { "Moscow", "Berlin", "Shenzhen" }; - for (int i = 0; i < 3; i++) - { - for (int j = 0; j < 3; j++) + var netmap = new FrostFsNetmapSnapshot(100, nodes); + + Assert.NotNull(testCase.Tests); + + foreach (var test in testCase.Tests) { - var attr = new Dictionary { { "City", cities[i] } }; - var key = new byte[] { (byte)(i * 4 + j) }; - var node = new FrostFsNodeInfo(version, NodeState.Online, [], attr, key); + _testOutputHelper.WriteLine($"Start test \"{test.Name}\""); - nodes.Add(node); - } - } + var policy = new FrostFsPlacementPolicy( + test.Policy!.Unique, + test.Policy.ContainerBackupFactor, + new Collection(test.Policy.Selectors?.Select(s => s.Selector).ToList() ?? []), + new Collection(test.Policy.Filters?.Select(f => f.Filter).ToList() ?? []), + test.Policy.Replicas?.Select(r => new FrostFsReplica(r.Count, r.Selector)).ToArray() ?? [] + ); - var netMap = new FrostFsNetmapSnapshot(100, nodes.AsReadOnly()); - - var v = netMap.ContainerNodes(p, null); - - Assert.Equal(2, v.Length); - Assert.Equal(2, v[0].Length); - Assert.Equal(2, v[1].Length); - - - for (int i = 0; i < v.Length; i++) - { - foreach (var ni in v[i]) - { - for (int j = 0; j < i; j++) + try { - foreach (var nj in v[j]) + var result = netmap.ContainerNodes(policy, test.PivotBytes); + + if (test.Result == null) { - Assert.NotEqual(ni.Hash, nj.Hash); + if (!string.IsNullOrEmpty(test.Error)) + { + Assert.Fail("Error is expected but has not been thrown"); + } + else + { + Assert.NotNull(test.Policy?.Replicas); + Assert.Equal(result.Length, test.Policy.Replicas.Length); + + for (int i = 0; i < result.Length; i++) + { + Assert.Empty(result[i]); + } + } + } + else + { + Assert.Equal(test.Result.Length, result.Length); + + for (var i = 0; i < test.Result.Length; i++) + { + Assert.Equal(test.Result[i].Length, result[i].Length); + for (var j = 0; j < test.Result[i].Length; j++) + { + CompareNodes(nodes[test.Result[i][j]].Attributes, result[i][j]); + } + } + + if (test.Placement?.Result != null && test.Placement.PivotBytes != null) + { + var placementResult = netmap.PlacementVectors(result, test.Placement.PivotBytes); + + Assert.Equal(test.Placement.Result.Length, placementResult.Length); + + for (int i = 0; i < placementResult.Length; i++) + { + Assert.Equal(test.Placement.Result[i].Length, placementResult[i].Length); + for (int j = 0; j < placementResult[i].Length; j++) + { + CompareNodes(nodes[test.Placement.Result[i][j]].Attributes, placementResult[i][j]); + } + } + } } } + catch (Exception ex) + { + if (!string.IsNullOrEmpty(test.Error)) + { + Assert.Contains(test.Error, ex.Message, StringComparison.InvariantCulture); + } + else + { + throw; + } + } + + _testOutputHelper.WriteLine($"Done"); } } } + + + private static void CompareNodes(IReadOnlyDictionary attrs, FrostFsNodeInfo nodeInfo) + { + Assert.Equal(attrs.Count, nodeInfo.Attributes.Count); + Assert.True(attrs.OrderBy(k => k.Key).SequenceEqual(nodeInfo.Attributes.OrderBy(x => x.Key))); + } +} + +public class TestCase +{ + public string? Name { get; set; } + + public Node[]? Nodes { get; set; } + + public TestData[]? Tests { get; set; } +} + + + +public class Node +{ + [JsonPropertyName("attributes")] + public KeyValuePair[]? Attributes { get; set; } + + public string? PublicKey { get; set; } + + internal byte[]? PublicKeyBytes => string.IsNullOrEmpty(PublicKey) ? [] : Convert.FromBase64String(PublicKey); + + public string[]? Addresses { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public NodeState State { get; set; } = NodeState.Online; +} + +public class TestData +{ + public string? Name { get; set; } + + public PolicyDto? Policy { get; set; } + + public string? Pivot { get; set; } + + public int[][]? Result { get; set; } + + public string? Error { get; set; } + + internal byte[]? PivotBytes => Pivot != null ? Convert.FromBase64String(Pivot) : null; + + public ResultData? Placement { get; set; } +} + +public class PolicyDto +{ + public bool Unique { get; set; } + + public uint ContainerBackupFactor { get; set; } + + public FilterDto[]? Filters { get; set; } + + public ReplicaDto[]? Replicas { get; set; } + + public SelectorDto[]? Selectors { get; set; } +} + +public class SelectorDto() +{ + public uint Count { get; set; } + + public string? Name { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public ClauseValues Clause { get; set; } + + public string? Attribute { get; set; } + + public string? Filter { get; set; } + + public FrostFsSelector Selector => new(Name ?? string.Empty) + { + Count = Count, + Clause = (int)Clause, + Filter = Filter, + Attribute = Attribute + }; +} + +public class FilterDto +{ + public string? Name { get; set; } + + public string? Key { get; set; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public Operation Op { get; set; } + + public string? Value { get; set; } + + public FilterDto[]? Filters { get; set; } + + public FrostFsFilter Filter => new( + Name ?? string.Empty, + Key ?? string.Empty, + (int)Op, + Value ?? string.Empty, + Filters != null ? Filters.Select(f => f.Filter).ToArray() : []); +} + +public class ReplicaDto +{ + public int Count { get; set; } + + public string? Selector { get; set; } +} + +public class ResultData +{ + public string? Pivot { get; set; } + + public int[][]? Result { get; set; } + + internal byte[]? PivotBytes => Pivot != null ? Convert.FromBase64String(Pivot) : null; +} + +public enum ClauseValues +{ + UNSPECIFIED = 0, + SAME, + DISTINCT } diff --git a/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs index 3706a91..eef8a45 100644 --- a/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/SessionTestsBase.cs @@ -30,7 +30,7 @@ public abstract class SessionTestsBase Mocker = new SessionMocker(key) { - PlacementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)), + PlacementPolicy = new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), Version = new FrostFsVersion(2, 13) }; } From 195854a45b8bc92670bfc6117b190da38c4d8567 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 12 Feb 2025 04:20:01 +0300 Subject: [PATCH 40/65] [#30] Client: Add object model for Rules Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/ApeRules/Actions.cs | 36 +++ .../{Models/Chain => ApeRules}/ChainTarget.cs | 0 src/FrostFS.SDK.Client/ApeRules/Condition.cs | 43 +++ .../ApeRules/Enums/ConditionKindType.cs | 7 + .../ApeRules/Enums/ConditionType.cs | 36 +++ .../Enums}/FrostFsTargetType.cs | 2 +- .../ApeRules/Enums/Status.cs | 9 + .../ApeRules/FrostFsChain.cs | 10 + .../ApeRules/FrostFsRule.cs | 18 ++ src/FrostFS.SDK.Client/ApeRules/MatchType.cs | 10 + src/FrostFS.SDK.Client/ApeRules/Resources.cs | 36 +++ .../ApeRules/RuleSerializer.cs | 286 ++++++++++++++++++ .../Exceptions/FrostFsResponseException.cs | 3 +- src/FrostFS.SDK.Client/Mappers/Status.cs | 6 +- .../Models/Chain/FrostFsChain.cs | 41 --- .../Models/Containers/FrostFsContainerId.cs | 21 +- .../Models/Response/FrostFsResponseStatus.cs | 8 +- .../Parameters/PrmApeChainAdd.cs | 4 +- .../Parameters/PrmApeChainList.cs | 4 +- .../Parameters/PrmApeChainRemove.cs | 13 +- .../Services/ApeManagerServiceProvider.cs | 9 +- .../Services/ContainerServiceProvider.cs | 2 +- .../Services/ObjectServiceProvider.cs | 9 +- src/FrostFS.SDK.Client/Tools/Verifier.cs | 16 +- .../Smoke/SmokeClientTests.cs | 191 +++++++----- src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 10 +- .../Unit/PlacementVectorTests.cs | 2 +- 27 files changed, 677 insertions(+), 155 deletions(-) create mode 100644 src/FrostFS.SDK.Client/ApeRules/Actions.cs rename src/FrostFS.SDK.Client/{Models/Chain => ApeRules}/ChainTarget.cs (100%) create mode 100644 src/FrostFS.SDK.Client/ApeRules/Condition.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/Enums/ConditionKindType.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs rename src/FrostFS.SDK.Client/{Models/Chain => ApeRules/Enums}/FrostFsTargetType.cs (86%) create mode 100644 src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/MatchType.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/Resources.cs create mode 100644 src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs delete mode 100644 src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs diff --git a/src/FrostFS.SDK.Client/ApeRules/Actions.cs b/src/FrostFS.SDK.Client/ApeRules/Actions.cs new file mode 100644 index 0000000..28600b8 --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Actions.cs @@ -0,0 +1,36 @@ +namespace FrostFS.SDK.Client; + +public struct Actions(bool inverted, string[] names) : System.IEquatable +{ + 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()); + } +} diff --git a/src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs b/src/FrostFS.SDK.Client/ApeRules/ChainTarget.cs similarity index 100% rename from src/FrostFS.SDK.Client/Models/Chain/ChainTarget.cs rename to src/FrostFS.SDK.Client/ApeRules/ChainTarget.cs diff --git a/src/FrostFS.SDK.Client/ApeRules/Condition.cs b/src/FrostFS.SDK.Client/ApeRules/Condition.cs new file mode 100644 index 0000000..11c1f7a --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Condition.cs @@ -0,0 +1,43 @@ +namespace FrostFS.SDK.Client; + +public struct Condition : System.IEquatable +{ + 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()); + } +} diff --git a/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionKindType.cs b/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionKindType.cs new file mode 100644 index 0000000..fd13893 --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionKindType.cs @@ -0,0 +1,7 @@ +namespace FrostFS.SDK.Client; + +public enum ConditionKindType +{ + Resource, + Request +} diff --git a/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs b/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs new file mode 100644 index 0000000..a5f6ab4 --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Enums/ConditionType.cs @@ -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, +} diff --git a/src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs b/src/FrostFS.SDK.Client/ApeRules/Enums/FrostFsTargetType.cs similarity index 86% rename from src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs rename to src/FrostFS.SDK.Client/ApeRules/Enums/FrostFsTargetType.cs index d5379f8..178ca63 100644 --- a/src/FrostFS.SDK.Client/Models/Chain/FrostFsTargetType.cs +++ b/src/FrostFS.SDK.Client/ApeRules/Enums/FrostFsTargetType.cs @@ -2,7 +2,7 @@ public enum FrostFsTargetType { - Undefined = 0, + Undefined, Namespace, Container, User, diff --git a/src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs b/src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs new file mode 100644 index 0000000..547449b --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Enums/Status.cs @@ -0,0 +1,9 @@ +namespace FrostFS.SDK.Client; + +public enum RuleStatus +{ + Allow, + NoRuleFound, + AccessDenied, + QuotaLimitReached +} diff --git a/src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs b/src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs new file mode 100644 index 0000000..c79bd7d --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/FrostFsChain.cs @@ -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; } +} diff --git a/src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs b/src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs new file mode 100644 index 0000000..125e3f1 --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/FrostFsRule.cs @@ -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; } +} diff --git a/src/FrostFS.SDK.Client/ApeRules/MatchType.cs b/src/FrostFS.SDK.Client/ApeRules/MatchType.cs new file mode 100644 index 0000000..fe3305e --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/MatchType.cs @@ -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 +} diff --git a/src/FrostFS.SDK.Client/ApeRules/Resources.cs b/src/FrostFS.SDK.Client/ApeRules/Resources.cs new file mode 100644 index 0000000..55849af --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/Resources.cs @@ -0,0 +1,36 @@ +namespace FrostFS.SDK.Client; + +public struct Resource(bool inverted, string[] names) : System.IEquatable +{ + 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()); + } +} diff --git a/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs b/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs new file mode 100644 index 0000000..d5602f3 --- /dev/null +++ b/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs @@ -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; + + /// + /// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77 + /// + 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[] slice, Func 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(byte[] buf, int offset, T[] slice, Func 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"); + } + } +} diff --git a/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs index 61f4e98..0e64db5 100644 --- a/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs @@ -10,7 +10,8 @@ public class FrostFsResponseException : FrostFsException { } - public FrostFsResponseException(FrostFsResponseStatus status) + public FrostFsResponseException(FrostFsResponseStatus status) + : base(status != null ? status.Message != null ? "" : "" : "") { Status = status; } diff --git a/src/FrostFS.SDK.Client/Mappers/Status.cs b/src/FrostFS.SDK.Client/Mappers/Status.cs index f39b1fe..f3da090 100644 --- a/src/FrostFS.SDK.Client/Mappers/Status.cs +++ b/src/FrostFS.SDK.Client/Mappers/Status.cs @@ -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])))); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs b/src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs deleted file mode 100644 index 70bf093..0000000 --- a/src/FrostFS.SDK.Client/Models/Chain/FrostFsChain.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Google.Protobuf; - -namespace FrostFS.SDK.Client; - -public struct FrostFsChain(byte[] raw) : System.IEquatable -{ - 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; - } -} diff --git a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs index 9f081cd..e91e1b8 100644 --- a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerId.cs @@ -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() diff --git a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs index b5e5831..2f48ba3 100644 --- a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs +++ b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs @@ -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}"; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs index adcc1b9..54b5171 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs @@ -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 { diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs index a946840..57c90bd 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs @@ -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 { diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs index 7e31ff4..1fc110b 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs @@ -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 { public FrostFsChainTarget Target { get; } = target; - public FrostFsChain Chain { get; } = chain; + public byte[] ChainId { get; } = chainId; /// /// 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) diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 0484b16..174d460 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -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> 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() } }; diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index 6e02f10..ca76e08 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -39,7 +39,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async Task 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); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 246c3eb..3125547 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -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 }, diff --git a/src/FrostFS.SDK.Client/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs index ce215ba..063fa21 100644 --- a/src/FrostFS.SDK.Client/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -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); + } + } } /// @@ -138,6 +144,8 @@ public static class Verifier } if (!request.Verify()) + { throw new FrostFsResponseException($"invalid response, type={request.GetType()}"); + } } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs index d774b47..e1b0c8d 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs @@ -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); + } } diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index 5a9942d..95e353a 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -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; } diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs index 98d3ee9..fdf7ffe 100644 --- a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs @@ -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); From 2e56c139465c601c5c5bdb5a27696dcce376b511 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 17 Feb 2025 16:01:41 +0300 Subject: [PATCH 41/65] [#31] Client: fix for session Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/CllientKey.cs | 2 +- src/FrostFS.SDK.Client/FrostFSClient.cs | 2 +- .../Models/Netmap/FrostFsPlacementPolicy.cs | 3 +- .../Models/Object/FrostFsObject.cs | 3 +- .../Models/Session/FrostFsSessionToken.cs | 15 +- src/FrostFS.SDK.Client/ObjectWriter.cs | 2 + .../Services/ObjectServiceProvider.cs | 84 +++--- .../Tools/RequestConstructor.cs | 5 +- .../Smoke/SmokeClientTests.cs | 268 +++++++++++------- src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 9 +- 10 files changed, 247 insertions(+), 146 deletions(-) diff --git a/src/FrostFS.SDK.Client/CllientKey.cs b/src/FrostFS.SDK.Client/CllientKey.cs index da02b02..7a7943b 100644 --- a/src/FrostFS.SDK.Client/CllientKey.cs +++ b/src/FrostFS.SDK.Client/CllientKey.cs @@ -12,7 +12,7 @@ public class ClientKey(ECDsa key) internal ByteString PublicKeyProto { get; } = ByteString.CopyFrom(key.PublicKey()); - internal string PublicKey { get; } = key.PublicKey().ToString(); + internal string PublicKey { get; } = Base58.Encode(key.PublicKey()); internal FrostFsOwner Owner { get; } = new FrostFsOwner(key.PublicKey().PublicKeyToAddress()); } diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index eea24fc..27017fe 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -447,7 +447,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - ObjectServiceClient = ObjectServiceClient ?? ( + ObjectServiceClient ??= ( invoker != null ? new ObjectServiceClient(invoker) : new ObjectServiceClient(ClientCtx.Channel)); diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index 6ca8ab9..184514b 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -47,7 +47,8 @@ public struct FrostFsPlacementPolicy(bool unique, Filters = { }, Selectors = { }, Replicas = { }, - Unique = Unique + Unique = Unique, + ContainerBackupFactor = BackupFactor }; foreach (var replica in Replicas) diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs index f366b9e..dfd2a22 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs @@ -45,9 +45,10 @@ public class FrostFsObject /// Reader for received data public IObjectReader? ObjectReader { get; set; } - internal byte[] SingleObjectPayload + public byte[] SingleObjectPayload { get { return bytes ?? []; } + set { bytes = value; } } /// diff --git a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs index 54f0615..5f675ae 100644 --- a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs +++ b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs @@ -1,5 +1,5 @@ using System; - +using System.Diagnostics; using FrostFS.Refs; using FrostFS.SDK.Client; @@ -36,7 +36,9 @@ public class FrostFsSessionToken get { if (_id == Guid.Empty) + { _id = ProtoId.ToUuid(); + } return _id; } @@ -47,7 +49,9 @@ public class FrostFsSessionToken get { if (_sessionKey.IsEmpty) + { _sessionKey = ProtoSessionKey.Memory; + } return _sessionKey; } @@ -69,12 +73,15 @@ public class FrostFsSessionToken sessionToken.Body.Container = new() { Verb = verb }; if (containerId != null) + { sessionToken.Body.Container.ContainerId = containerId; + } else + { sessionToken.Body.Container.Wildcard = true; + } sessionToken.Body.SessionKey = key.PublicKeyProto; - sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); return sessionToken; @@ -100,7 +107,9 @@ public class FrostFsSessionToken ObjectSessionContext.Types.Target target = new() { Container = address.ContainerId }; if (address.ObjectId != null) + { target.Objects.Add(address.ObjectId); + } sessionToken.Body.Object = new() { @@ -108,8 +117,6 @@ public class FrostFsSessionToken Verb = verb }; - sessionToken.Body.SessionKey = key.PublicKeyProto; - sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); return sessionToken; diff --git a/src/FrostFS.SDK.Client/ObjectWriter.cs b/src/FrostFS.SDK.Client/ObjectWriter.cs index 4fad9c9..5683740 100644 --- a/src/FrostFS.SDK.Client/ObjectWriter.cs +++ b/src/FrostFS.SDK.Client/ObjectWriter.cs @@ -32,6 +32,8 @@ namespace FrostFS.SDK.Client } }; + chunkRequest.AddMetaHeader(args.XHeaders); + chunkRequest.Sign(this.ctx.Key.ECDsaKey); await streamer.Write(chunkRequest).ConfigureAwait(false); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 3125547..1011b69 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -10,7 +10,6 @@ using FrostFS.Refs; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using FrostFS.Session; using Google.Protobuf; @@ -278,7 +277,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); var protoToken = sessionToken.CreateObjectTokenContext( - new Address { ContainerId = grpcObject.Header.ContainerId, ObjectId = grpcObject.ObjectId }, + new Address { ContainerId = grpcObject.Header.ContainerId }, ObjectSessionContext.Types.Verb.Put, ClientContext.Key); @@ -297,7 +296,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var chunkSize = args.MaxChunkLength; Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); - + var call = client.Patch(null, ctx.GetDeadline(), ctx.CancellationToken); byte[]? chunkBuffer = null; @@ -306,31 +305,15 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl // common chunkBuffer = ArrayPool.Shared.Rent(chunkSize); + bool isFirstChunk = true; + ulong currentPos = args.Range.Offset; + var address = new Address { ObjectId = args.Address.ObjectId, ContainerId = args.Address.ContainerId }; - var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - - var protoToken = sessionToken.CreateObjectTokenContext( - address, - ObjectSessionContext.Types.Verb.Patch, - ClientContext.Key); - - var request = new PatchRequest() - { - Body = new() - { - Address = address, - ReplaceAttributes = args.ReplaceAttributes, - } - }; - - bool isFirstChunk = true; - ulong currentPos = args.Range.Offset; - while (true) { var bytesCount = await payload.ReadAsync(chunkBuffer, 0, chunkSize, ctx.CancellationToken).ConfigureAwait(false); @@ -340,41 +323,62 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl break; } - if (isFirstChunk && args.NewAttributes != null && args.NewAttributes.Length > 0) + var request = new PatchRequest() { - foreach (var attr in args.NewAttributes) + Body = new() { - request.Body.NewAttributes.Add(attr.ToMessage()); + Address = address, + Patch = new PatchRequest.Types.Body.Types.Patch + { + Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount), + SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount } + } } - } - - request.Body.Patch = new PatchRequest.Types.Body.Types.Patch - { - Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount), - SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount } }; - currentPos += (ulong)bytesCount; + if (isFirstChunk) + { + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - request.AddMetaHeader(args.XHeaders, protoToken); + var protoToken = sessionToken.CreateObjectTokenContext( + address, + ObjectSessionContext.Types.Verb.Patch, + ClientContext.Key); + + request.AddMetaHeader(args.XHeaders, protoToken); + + if (args.NewAttributes != null && args.NewAttributes.Length > 0) + { + foreach (var attr in args.NewAttributes) + { + request.Body.NewAttributes.Add(attr.ToMessage()); + request.Body.ReplaceAttributes = args.ReplaceAttributes; + } + } + + isFirstChunk = false; + } + else + { + request.AddMetaHeader(args.XHeaders); + } request.Sign(ClientContext.Key.ECDsaKey); await call.RequestStream.WriteAsync(request).ConfigureAwait(false); - isFirstChunk = false; + currentPos += (ulong)bytesCount; } } finally { - await call.RequestStream.CompleteAsync().ConfigureAwait(false); - if (chunkBuffer != null) { ArrayPool.Shared.Return(chunkBuffer); } } + await call.RequestStream.CompleteAsync().ConfigureAwait(false); var response = await call.ResponseAsync.ConfigureAwait(false); Verifier.CheckResponse(response); @@ -513,7 +517,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { // send chunks limited to default or user's settings var bufferSize = objectLimitSize > 0 ? - (int)Math.Min(objectLimitSize - sentBytes, chunkSize) + Math.Min(objectLimitSize - sentBytes, chunkSize) : chunkSize; var bytesCount = await payload.ReadAsync(chunkBuffer, 0, bufferSize, ctx.CancellationToken).ConfigureAwait(false); @@ -531,6 +535,8 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } }; + chunkRequest.AddMetaHeader(args.XHeaders); + chunkRequest.Sign(ClientContext.Key.ECDsaKey); await stream.Write(chunkRequest).ConfigureAwait(false); @@ -571,8 +577,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl ObjectTools.SetSplitValues(grpcHeader, header.Split, ClientContext.Owner, ClientContext.Version, ClientContext.Key); } - var oid = new ObjectID { Value = grpcHeader.Sha256() }; - var initRequest = new PutRequest { Body = new PutRequest.Types.Body @@ -587,7 +591,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); var protoToken = sessionToken.CreateObjectTokenContext( - new Address { ContainerId = grpcHeader.ContainerId, ObjectId = oid }, + new Address { ContainerId = grpcHeader.ContainerId }, ObjectSessionContext.Types.Verb.Put, ClientContext.Key); diff --git a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs index 00a3916..102eeb5 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestConstructor.cs @@ -18,7 +18,10 @@ public static class RequestConstructor if (request.MetaHeader is not null) return; - request.MetaHeader = MetaHeader.Default().ToMessage(); + var metaHeader = MetaHeader.Default(); + metaHeader.Ttl = 2; + + request.MetaHeader = metaHeader.ToMessage(); if (sessionToken != null) request.MetaHeader.SessionToken = sessionToken; diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs index e1b0c8d..3dac829 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs @@ -7,6 +7,7 @@ using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.SmokeTests; +using FrostFS.Session; using Google.Protobuf; using Microsoft.Extensions.Options; @@ -21,6 +22,8 @@ public class SmokeClientTests : SmokeTestsBase [Fact] public async void AccountTest() { + var test = lightWait.Timeout; + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); var result = await client.GetBalanceAsync(default); @@ -66,8 +69,6 @@ public class SmokeClientTests : SmokeTestsBase var token = await client.CreateSessionAsync(new(100), default); - var ownerHash = Base58.Decode(OwnerId!.Value); - Assert.NotNull(token); Assert.NotEqual(Guid.Empty, token.Id); Assert.Equal(33, token.SessionKey.Length); @@ -84,7 +85,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["key1", "value1"]); @@ -133,7 +134,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), lightWait); @@ -221,7 +222,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -285,6 +286,81 @@ public class SmokeClientTests : SmokeTestsBase } Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + } + + [Theory] + [InlineData(1)] + [InlineData(500 * 1024)] + [InlineData(3 * 1024 * 1024)] + public async void PutSingleObjectTest(int objectSize) + { + var options = ClientOptions; + + var client = FrostFSClient.GetInstance(options, GrpcChannel); + + await Cleanup(client); + + var ctx = new CallContext(); + + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo( + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), + [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), + PrmWait.DefaultParams, + xheaders: ["testKey1", "testValue1"]); + + var containerId = await client.CreateContainerAsync(createContainerParam, ctx); + + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); + Assert.NotNull(container); + + await AddObjectRules(client, containerId); + + var bytes = GetRandomBytes(objectSize); + + var header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName1", "test1")]); + + var obj = new FrostFsObject(header) { SingleObjectPayload = bytes }; + + var param = new PrmSingleObjectPut(obj); + + var objectId = await client.PutSingleObjectAsync(param, default).ConfigureAwait(true); + + var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName1", "test1"); + + bool hasObject = false; + await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) + { + hasObject = true; + + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); + Assert.NotNull(objHeader.Attributes); + Assert.Single(objHeader.Attributes); + Assert.Equal("fileName1", objHeader.Attributes.First().Key); + Assert.Equal("test1", objHeader.Attributes.First().Value); + } + + Assert.True(hasObject); + + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); await Cleanup(client); @@ -294,6 +370,14 @@ public class SmokeClientTests : SmokeTestsBase } } + [Fact] + public async void CleanupTest() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + await Cleanup(client); + } + [Fact] public async void PatchTest() { @@ -387,7 +471,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -429,13 +513,6 @@ public class SmokeClientTests : SmokeTestsBase } Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(50, 100)), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } } [Fact] @@ -447,7 +524,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); @@ -482,13 +559,8 @@ public class SmokeClientTests : SmokeTestsBase foreach (var hash in hashes) { var x = hash[..32].ToArray(); - } - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); + Assert.NotNull(x); + Assert.True(x.Length > 0); } } @@ -499,7 +571,7 @@ public class SmokeClientTests : SmokeTestsBase public async void SimpleScenarioWithSessionTest(int objectSize) { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) + var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); await Cleanup(client); @@ -508,7 +580,7 @@ public class SmokeClientTests : SmokeTestsBase var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), PrmWait.DefaultParams); @@ -574,14 +646,8 @@ public class SmokeClientTests : SmokeTestsBase } } - [Theory] - [InlineData(1)] - [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB - [InlineData(64 * 1024 * 1024 - 1)] - [InlineData(64 * 1024 * 1024 + 1)] - [InlineData(2 * 64 * 1024 * 1024 + 256)] - [InlineData(200)] - public async void ClientCutScenarioTest(int objectSize) + [Fact] + public async void ClientCutScenarioTest() { var options = ClientOptions; options.Value.Interceptors.Add(new CallbackInterceptor()); @@ -590,71 +656,57 @@ public class SmokeClientTests : SmokeTestsBase await Cleanup(client); - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - lightWait); + int[] replicas = [3]; //1 - var containerId = await client.CreateContainerAsync(createContainerParam, default); - - var ctx = new CallContext(TimeSpan.FromSeconds(10)); - - var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); - - Assert.NotNull(container); - - await AddObjectRules(client, containerId); - - byte[] bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectClientCutPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes)); - - var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); - - var filter1 = new FilterByOwnerId(FrostFsMatchType.Equals, OwnerId!); - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter1), default)) + foreach (var rep in replicas) { - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default); - } + var createContainerParam = new PrmContainerCreate( + new FrostFsContainerInfo( + new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(rep)), + [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), + lightWait); - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); + var containerId = await client.CreateContainerAsync(createContainerParam, default); - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); + var ctx = new CallContext(TimeSpan.FromSeconds(10)); - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + Assert.NotNull(container); - await CheckFilter(client, containerId, new FilterByRootObject()); + await AddObjectRules(client, containerId); + + var mb = 1024 * 1024; - await Cleanup(client); + int[] objectSizes = [1, 1024, 64 * mb + 1]; //64 * mb, 64 * mb - 1, , 2 * 64 * mb + 256]; - var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - - IAsyncEnumerator? enumerator = null; - do - { - if (deadline <= DateTime.UtcNow) + foreach (var objectSize in objectSizes) { - Assert.Fail("Containers exist"); - break; - } + byte[] bytes = GetRandomBytes(objectSize); - enumerator = client.ListContainersAsync(default, default).GetAsyncEnumerator(); - await Task.Delay(500); + var param = new PrmObjectClientCutPut( + new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]), + payload: new MemoryStream(bytes)); + + var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); + + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); + } } - while (await enumerator!.MoveNextAsync()); } [Fact] @@ -712,6 +764,43 @@ public class SmokeClientTests : SmokeTestsBase } } + private static async Task AddContainerRules(IFrostFSClient client, FrostFsContainerId containerId) + { + var addChainPrm = new PrmApeChainAdd( + new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()), + new FrostFsChain + { + ID = Encoding.ASCII.GetBytes("chain-id-test1"), + Rules = [ + new FrostFsRule + { + Status = RuleStatus.Allow, + Actions = new Actions(inverted: false, names: ["*"]), + Resources = new Resource (inverted: false, names: [$"native:container/*"]), + 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); + } + private static async Task AddObjectRules(IFrostFSClient client, FrostFsContainerId containerId) { var addChainPrm = new PrmApeChainAdd( @@ -735,17 +824,6 @@ public class SmokeClientTests : SmokeTestsBase 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); } } diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index 95e353a..a524a82 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -11,9 +11,14 @@ namespace FrostFS.SDK.Tests.Smoke; public abstract class SmokeTestsBase { + // cluster Ori + internal readonly string url = "http://10.78.128.207:8080"; + internal readonly string keyString = "L4JWLdedUd4b21sriRHtCPGkjG2Mryz2AWLiVqTBSNyxxyAUcc7s"; + + // cluster - internal readonly string url = "http://10.78.128.190:8080"; - internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; + // internal readonly string url = "http://10.78.128.190:8080"; + // internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; // WSL2 //internal readonly string url = "http://172.29.238.97:8080"; From bd8eb7cc60530538c0b42d7085dcd14ce66d6fbf Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 19 Feb 2025 10:55:57 +0300 Subject: [PATCH 42/65] [#33] Client: Add extended life tests Signed-off-by: Pavel Gross --- README.md | 2 +- src/FrostFS.SDK.Client/FrostFSClient.cs | 31 +- .../Interfaces/IFrostFSClient.cs | 3 + .../Mappers/Netmap/PlacementPolicy.cs | 6 +- .../Mappers/Netmap/Replica.cs | 13 +- .../Mappers/Object/ObjectAttributeMapper.cs | 5 - .../Models/Netmap/FrostFsFilter.cs | 20 +- .../Models/Netmap/FrostFsPlacementPolicy.cs | 10 + .../Models/Netmap/FrostFsSelector.cs | 18 +- .../Models/Object/FrostFsAttributePair.cs | 30 +- src/FrostFS.SDK.Client/Pool/Pool.cs | 9 +- .../Services/ContainerServiceProvider.cs | 2 +- .../Client/ContainerTests/ContainerTests.cs | 283 ++++++ .../Smoke/Client/MiscTests/InterceptorTest.cs | 37 + .../Smoke/Client/NetworkTests/NetworkTests.cs | 111 +++ .../Smoke/Client/ObjectTests/ObjectTests.cs | 337 +++++++ .../Smoke/Client/SessionTests/SessionTests.cs | 21 + .../Multithread/MultiThreadTestsBase.cs | 0 .../Multithread/MultithreadPoolSmokeTests.cs | 47 +- .../MultithreadSmokeClientTests.cs | 44 +- .../Smoke/{ => PoolTests}/PoolSmokeTests.cs | 28 +- .../Smoke/SmokeClientTests.cs | 829 ------------------ src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 108 ++- src/FrostFS.SDK.Tests/Unit/ContainerTest.cs | 2 +- 24 files changed, 1020 insertions(+), 976 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs create mode 100644 src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs create mode 100644 src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs create mode 100644 src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs create mode 100644 src/FrostFS.SDK.Tests/Smoke/Client/SessionTests/SessionTests.cs rename src/FrostFS.SDK.Tests/{ => Smoke/PoolTests}/Multithread/MultiThreadTestsBase.cs (100%) rename src/FrostFS.SDK.Tests/{ => Smoke/PoolTests}/Multithread/MultithreadPoolSmokeTests.cs (92%) rename src/FrostFS.SDK.Tests/{ => Smoke/PoolTests}/Multithread/MultithreadSmokeClientTests.cs (94%) rename src/FrostFS.SDK.Tests/Smoke/{ => PoolTests}/PoolSmokeTests.cs (95%) delete mode 100644 src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs diff --git a/README.md b/README.md index eccf0a2..add47ea 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ var placementPolicy = new FrostFsPlacementPolicy(true, new FrostFsReplica(1)); var createContainerParam = new PrmContainerCreate( new FrostFsContainerInfo(BasicAcl.PublicRW, new FrostFsPlacementPolicy(true, new FrostFsReplica(1)))); -var containerId = await client.CreateContainerAsync(createContainerParam); +var containerId = await client.PutContainerAsync(createContainerParam); using var fileStream = File.OpenRead(@"C:\Users\Paul\Pictures\cat.jpeg"); diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 27017fe..83f917e 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -212,9 +212,15 @@ public class FrostFSClient : IFrostFSClient return GetContainerService().ListContainersAsync(args, ctx); } + [Obsolete("Use PutContainerAsync method")] public Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { - return GetContainerService().CreateContainerAsync(args, ctx); + return GetContainerService().PutContainerAsync(args, ctx); + } + + public Task PutContainerAsync(PrmContainerCreate args, CallContext ctx) + { + return GetContainerService().PutContainerAsync(args, ctx); } public Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) @@ -314,18 +320,6 @@ public class FrostFSClient : IFrostFSClient } #endregion - private async void CheckFrostFsVersionSupport(CallContext ctx) - { - var service = GetNetmapService(); - var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx).ConfigureAwait(false); - - if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) - { - var msg = $"FrostFS {localNodeInfo.Version} is not supported."; - throw new FrostFsException(msg); - } - } - private CallInvoker? CreateInvoker() { CallInvoker? callInvoker = null; @@ -361,7 +355,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - NetmapServiceClient = NetmapServiceClient ?? ( + NetmapServiceClient ??= ( invoker != null ? new NetmapServiceClient(invoker) : new NetmapServiceClient(ClientCtx.Channel)); @@ -378,7 +372,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - SessionServiceClient = SessionServiceClient ?? ( + SessionServiceClient ??= ( invoker != null ? new SessionServiceClient(invoker) : new SessionServiceClient(ClientCtx.Channel)); @@ -387,7 +381,6 @@ public class FrostFSClient : IFrostFSClient } return SessionServiceProvider; - } private ApeManagerServiceProvider GetApeManagerService() @@ -396,7 +389,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - ApeManagerServiceClient = ApeManagerServiceClient ?? ( + ApeManagerServiceClient ??= ( invoker != null ? new APEManagerServiceClient(invoker) : new APEManagerServiceClient(ClientCtx.Channel)); @@ -413,7 +406,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - AccountingServiceClient = AccountingServiceClient ?? ( + AccountingServiceClient ??= ( invoker != null ? new AccountingServiceClient(invoker) : new AccountingServiceClient(ClientCtx.Channel)); @@ -430,7 +423,7 @@ public class FrostFSClient : IFrostFSClient { var invoker = CreateInvoker(); - ContainerServiceClient = ContainerServiceClient ?? ( + ContainerServiceClient ??= ( invoker != null ? new ContainerServiceClient(invoker) : new ContainerServiceClient(ClientCtx.Channel)); diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index cce7364..594cc63 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -33,8 +33,11 @@ public interface IFrostFSClient IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx); + [Obsolete("Use PutContainerAsync method")] Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx); + Task PutContainerAsync(PrmContainerCreate args, CallContext ctx); + Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx); #endregion diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs index 30410aa..ee1c8a4 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/PlacementPolicy.cs @@ -17,9 +17,9 @@ public static class PlacementPolicyMapper return new FrostFsPlacementPolicy( placementPolicy.Unique, placementPolicy.ContainerBackupFactor, - new System.Collections.ObjectModel.Collection(placementPolicy.Selectors.Select(selector => selector.ToModel()).ToList()), - new System.Collections.ObjectModel.Collection(placementPolicy.Filters.Select(filter => filter.ToModel()).ToList()), - placementPolicy.Replicas.Select(replica => replica.ToModel()).ToArray() + [.. placementPolicy.Selectors.Select(s => s.ToModel())], + [.. placementPolicy.Filters.Select(f => f.ToModel())], + [.. placementPolicy.Replicas.Select(r => r.ToModel())] ); } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs index bc415e1..cd7a0b7 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs @@ -50,13 +50,15 @@ public static class PolicyMapper throw new ArgumentNullException(nameof(selector)); } - return new FrostFsSelector(selector.Name) + var model = new FrostFsSelector(selector.Name) { Count = selector.Count, Clause = (int)selector.Clause, Attribute = selector.Attribute, Filter = selector.Filter }; + + return model; } public static Filter ToMessage(this FrostFsFilter filter) @@ -86,6 +88,13 @@ public static class PolicyMapper throw new ArgumentNullException(nameof(filter)); } - return new FrostFsFilter(filter.Name, filter.Key, (int)filter.Op, filter.Value, filter.Filters.Select(f => f.ToModel()).ToArray()); + var model = new FrostFsFilter( + filter.Name, + filter.Key, + (int)filter.Op, + filter.Value, + [.. filter.Filters.Select(f => f.ToModel())]); + + return model; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs index cfec8fd..a28100e 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectAttributeMapper.cs @@ -8,11 +8,6 @@ public static class ObjectAttributeMapper { public static Header.Types.Attribute ToMessage(this FrostFsAttributePair attribute) { - if (attribute is null) - { - throw new ArgumentNullException(nameof(attribute)); - } - return new Header.Types.Attribute { Key = attribute.Key, diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs index ebd3c20..11ee079 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsFilter.cs @@ -1,4 +1,7 @@ -namespace FrostFS.SDK; +using System.Linq; +using FrostFS.Netmap; + +namespace FrostFS.SDK; public class FrostFsFilter(string name, string key, int operation, string value, FrostFsFilter[] filters) : IFrostFsFilter { @@ -7,4 +10,19 @@ public class FrostFsFilter(string name, string key, int operation, string value, public int Operation { get; } = operation; public string Value { get; } = value; public FrostFsFilter[] Filters { get; } = filters; + + internal Filter GetMessage() + { + var filter = new Filter() + { + Name = Name, + Key = Key, + Op = (Operation)Operation, + Value = Value, + }; + + filter.Filters.AddRange(Filters.Select(f => f.GetMessage())); + + return filter; + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index 184514b..abfce6c 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -51,6 +51,16 @@ public struct FrostFsPlacementPolicy(bool unique, ContainerBackupFactor = BackupFactor }; + if (Selectors != null && Selectors.Count > 0) + { + policy.Selectors.AddRange(Selectors.Select(s => s.GetMessage())); + } + + if (Filters != null && Filters.Count > 0) + { + policy.Filters.AddRange(Filters.Select(s => s.ToMessage())); + } + foreach (var replica in Replicas) { policy.Replicas.Add(replica.ToMessage()); diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs index 3369d6c..c058967 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsSelector.cs @@ -1,10 +1,24 @@ -namespace FrostFS.SDK; +using FrostFS.Netmap; + +namespace FrostFS.SDK; public class FrostFsSelector(string name) { - public string Name { get; set; } = name; + public string Name { get; } = name; public uint Count { get; set; } public int Clause { get; set; } public string? Attribute { get; set; } public string? Filter { get; set; } + + internal Selector GetMessage() + { + return new Selector() + { + Name = Name, + Clause = (Clause)Clause, + Count = Count, + Filter = Filter ?? string.Empty, + Attribute = Attribute ?? string.Empty, + }; + } } diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs index 64d865f..9db7898 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsAttributePair.cs @@ -1,8 +1,36 @@ namespace FrostFS.SDK; -public class FrostFsAttributePair(string key, string value) +public struct FrostFsAttributePair(string key, string value) : System.IEquatable { public string Key { get; set; } = key; public string Value { get; set; } = value; + + public override bool Equals(object obj) + { + if (obj == null || obj is not FrostFsAttributePair) + return false; + + return Equals((FrostFsAttributePair)obj); + } + + public override int GetHashCode() + { + return Key.GetHashCode() ^ Value.GetHashCode(); + } + + public static bool operator ==(FrostFsAttributePair left, FrostFsAttributePair right) + { + return left.Equals(right); + } + + public static bool operator !=(FrostFsAttributePair left, FrostFsAttributePair right) + { + return !(left == right); + } + + public bool Equals(FrostFsAttributePair other) + { + return GetHashCode().Equals(other.GetHashCode()); + } } diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index 3abfe60..f0b7a6e 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -568,10 +568,17 @@ public partial class Pool : IFrostFSClient return client.Client!.ListContainersAsync(args, ctx); } + [Obsolete("Use PutContainerAsync method")] public async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) { var client = Connection(); - return await client.Client!.CreateContainerAsync(args, ctx).ConfigureAwait(false); + return await client.Client!.PutContainerAsync(args, ctx).ConfigureAwait(false); + } + + public async Task PutContainerAsync(PrmContainerCreate args, CallContext ctx) + { + var client = Connection(); + return await client.Client!.PutContainerAsync(args, ctx).ConfigureAwait(false); } public async Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index ca76e08..d7ba34c 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -71,7 +71,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService } } - internal async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) + internal async Task PutContainerAsync(PrmContainerCreate args, CallContext ctx) { var grpcContainer = args.Container.GetContainer(); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs new file mode 100644 index 0000000..37a8c13 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs @@ -0,0 +1,283 @@ +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using FrostFS.SDK.Client; +using Grpc.Core; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class ContainerTests : SmokeTestsBase +{ + [Fact] + public async void FailCreateContainerByTimeoutTest() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + try + { + _ = await CreateContainer(client, + ctx: new CallContext(TimeSpan.FromMilliseconds(1)), + token: null, + unique: true, + backupFactor: 1, + selectors: [], + filter: [], + containerAttributes: [new("testKey1", "testValue1")], + new FrostFsReplica(1)); + + Assert.Fail("Exception is expected"); + } + catch (RpcException ex) + { + Assert.Equal(StatusCode.DeadlineExceeded, ex.Status.StatusCode); + } + } + + [Fact] + public async void CreateContainerTest1() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + await Cleanup(client); + + client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + FrostFsContainerId containerId = await CreateContainer(client, + ctx: default, + token: null, + unique: true, + backupFactor: 1, + selectors: [], + filter: [], + containerAttributes: [new ("testKey1", "testValue1")], + new FrostFsReplica(3)); + + Assert.NotNull(containerId); + + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); + + Assert.NotNull(container); + + Assert.NotNull(container.Attributes); + Assert.Equal(2, container.Attributes.Count); + Assert.Equal("testKey1", container.Attributes[0].Key); + Assert.Equal("testValue1", container.Attributes[0].Value); + Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[1].Key); + Assert.Equal("true", container.Attributes[1].Value); + + Assert.True(container.PlacementPolicy.HasValue); + + Assert.Equal(1u, container.PlacementPolicy.Value.BackupFactor); + Assert.True(container.PlacementPolicy.Value.Unique); + Assert.Empty(container.PlacementPolicy.Value.Selectors); + Assert.Empty(container.PlacementPolicy.Value.Filters); + + Assert.Single(container.PlacementPolicy.Value.Replicas); + Assert.Equal(3, container.PlacementPolicy.Value.Replicas[0].Count); + Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcParityCount); + Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcDataCount); + Assert.Equal("", container.PlacementPolicy.Value.Replicas[0].Selector); + + Assert.Equal(OwnerId!.ToString(), container.Owner!.Value); + + Assert.NotNull(container.Version); + + Assert.Equal(Version!.Major, container.Version.Major); + Assert.Equal(Version.Minor, container.Version.Minor); + } + + [Fact] + public async void CreateContainerTest2() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + await Cleanup(client); + + client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + Collection filters = [ + new ("filter1", "filterKey1", 1, "testValue1", []), + new ("filter2", "filterKey2", 2, "testValue2", [new ("subFilter2", "subFilterKey2", 3, "testValue3",[])]) + ]; + + Collection selectors = [ + new ("selector1") { + Count = 1, + Clause = 1, + Attribute = "attribute1", + Filter = "filter1" + }, + new ("selector2") { + Count = 2, + Clause = 2, + Attribute = "attribute2", + Filter = "filter2" + }, + ]; + + FrostFsContainerId containerId = await CreateContainer(client, + ctx: default, + token: null, + unique: false, + backupFactor: 2, + selectors, + filter: filters, + containerAttributes: [], + new FrostFsReplica(1)); + + Assert.NotNull(containerId); + + var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); + + Assert.NotNull(container); + + Assert.NotNull(container.Attributes); + Assert.Single(container.Attributes); + Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[0].Key); + Assert.Equal("true", container.Attributes[0].Value); + + Assert.True(container.PlacementPolicy.HasValue); + + Assert.Equal(2u, container.PlacementPolicy.Value.BackupFactor); + Assert.False(container.PlacementPolicy.Value.Unique); + + Assert.NotEmpty(container.PlacementPolicy.Value.Selectors); + + Assert.Equal(2, container.PlacementPolicy.Value.Selectors.Count); + + var selector1 = container.PlacementPolicy.Value.Selectors[0]; + Assert.Equal("selector1", selector1.Name); + Assert.Equal(1, selector1.Clause); + Assert.Equal(1u, selector1.Count); + Assert.Equal("attribute1", selector1.Attribute); + Assert.Equal("filter1", selector1.Filter); + + var selector2 = container.PlacementPolicy.Value.Selectors[1]; + Assert.Equal("selector2", selector2.Name); + Assert.Equal(2, selector2.Clause); + Assert.Equal(2u, selector2.Count); + Assert.Equal("attribute2", selector2.Attribute); + Assert.Equal("filter2", selector2.Filter); + + Assert.NotEmpty(container.PlacementPolicy.Value.Filters); + + Assert.Equal(2, container.PlacementPolicy.Value.Filters.Count); + + var filter1 = container.PlacementPolicy.Value.Filters[0]; + Assert.Equal("filter1", filter1.Name); + Assert.Equal("filterKey1", filter1.Key); + Assert.Equal("testValue1", filter1.Value); + Assert.Equal(1, filter1.Operation); + Assert.Empty(filter1.Filters); + + var filter2 = container.PlacementPolicy.Value.Filters[1]; + Assert.Equal("filter2", filter2.Name); + Assert.Equal("filterKey2", filter2.Key); + Assert.Equal("testValue2", filter2.Value); + Assert.Equal(2, filter2.Operation); + Assert.NotEmpty(filter2.Filters); + + Assert.Single(filter2.Filters); + + var subFilter = filter2.Filters.First(); + Assert.Equal("subFilter2", subFilter.Name); + Assert.Equal("subFilterKey2", subFilter.Key); + Assert.Equal("testValue3", subFilter.Value); + Assert.Equal(3, subFilter.Operation); + Assert.Empty(subFilter.Filters); + + Assert.Single(container.PlacementPolicy.Value.Replicas); + Assert.Equal(1, container.PlacementPolicy.Value.Replicas[0].Count); + Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcParityCount); + Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcDataCount); + Assert.Equal("", container.PlacementPolicy.Value.Replicas[0].Selector); + + Assert.Equal(OwnerId!.ToString(), container.Owner!.Value); + + Assert.NotNull(container.Version); + + Assert.Equal(Version!.Major, container.Version.Major); + Assert.Equal(Version.Minor, container.Version.Minor); + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + public async void ListAndDeleteContainersTest(int countainerCount) + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + await Cleanup(client); + + var tasks = new Task[countainerCount]; + + var createdContainers = new ConcurrentDictionary(); + + for (int i = 0; i < countainerCount; i++) + { + int index = i; + tasks[i] = Task.Run(async () => + { + FrostFsContainerId containerId = await CreateContainer(client, + ctx: default, + token: null, + unique: true, + backupFactor: 1, + selectors: [], + filter: [], + containerAttributes: [new($"testKey{index}", $"testValue{index}")], + new FrostFsReplica(3)); + + createdContainers.TryAdd(containerId.ToString(), index); + }); + } + + #pragma warning disable xUnit1031 // Timeout is used + if (!Task.WaitAll(tasks, 20000)) + { + Assert.Fail("cannot create containers"); + } + #pragma warning restore xUnit1031 + + var containers = client.ListContainersAsync(new PrmContainerGetAll(), default); + + var receivedContainers = new List(); + await foreach (var cntr in containers) + { + receivedContainers.Add(cntr.ToString()); + } + + Assert.Equal(countainerCount, receivedContainers.Count); + + foreach (var cntrId in receivedContainers) + { + if (!createdContainers.TryGetValue(cntrId, out var index)) + { + Assert.Fail("Cannot find corresponding container"); + } + + FrostFsContainerId container = new(cntrId); + var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), default); + + Assert.NotNull(containerInfo); + Assert.NotNull(containerInfo.Attributes); + + Assert.Contains(new FrostFsAttributePair($"testKey{index}", $"testValue{index}"), containerInfo.Attributes); + } + + tasks = new Task[countainerCount]; + + for (int i = 0; i < receivedContainers.Count; i++) + { + tasks[i] = client.DeleteContainerAsync(new PrmContainerDelete(new FrostFsContainerId(receivedContainers[i]), lightWait), default); + } + + await Task.WhenAll(tasks); + + await foreach (var _ in client.ListContainersAsync(default, default)) + { + Assert.Fail("Containers exist"); + } + } +} diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs new file mode 100644 index 0000000..c3df8e5 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs @@ -0,0 +1,37 @@ +using FrostFS.SDK.Client; +using FrostFS.SDK.SmokeTests; + +namespace FrostFS.SDK.Tests.Smoke; + +public class InterceptorTests() : SmokeTestsBase +{ + [Fact] + public async void NodeInfoCallbackAndInterceptorTest() + { + bool callbackInvoked = false; + bool interceptorInvoked = false; + + var options = ClientOptions; + options.Value.Callback = (cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }; + + 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(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.NotNull(result.Addresses); + Assert.True(result.Attributes.Count > 0); + } +} diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs new file mode 100644 index 0000000..f1fce3c --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs @@ -0,0 +1,111 @@ +using System.Diagnostics.CodeAnalysis; +using FrostFS.SDK.Client; +using FrostFS.SDK.SmokeTests; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class SmokeClientTests : SmokeTestsBase +{ + [Fact] + public async void AccountTest() + { + var test = lightWait.Timeout; + + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + var result = await client.GetBalanceAsync(default); + + Assert.NotNull(result); + Assert.True(result.Value == 0); + } + + [Fact] + public async void NodeInfoTest() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + var result = await client.GetNodeInfoAsync(default); + + 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.Equal(2, result.Addresses.Count); + Assert.Equal(11, result.Attributes.Count); + } + + [Fact] + public async void NodeInfoStatisticsTest() + { + var options = ClientOptions; + + var callbackContent = string.Empty; + options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; + + var client = FrostFSClient.GetInstance(options, GrpcChannel); + + var result = await client.GetNodeInfoAsync(default); + + Assert.NotEmpty(callbackContent); + } + + [Fact] + public async void NetworkSettingsTest() + { + var options = ClientOptions; + var client = FrostFSClient.GetInstance(options, GrpcChannel); + + var result = await client.GetNetworkSettingsAsync(default); + + Assert.NotNull(result); + + Assert.True(0u < result.Epoch); + Assert.True(result.HomomorphicHashingDisabled); + Assert.True(result.MaintenanceModeAllowed); + Assert.True(0u < result.MagicNumber); + + Assert.Equal(0u, result.AuditFee); + Assert.Equal(0u, result.BasicIncomeRate); + Assert.Equal(0u, result.ContainerAliasFee); + Assert.Equal(0u, result.ContainerFee); + Assert.Equal(75u, result.EpochDuration); + Assert.Equal(10_000_000_000u, result.InnerRingCandidateFee); + Assert.Equal(12u, result.MaxECDataCount); + Assert.Equal(4u, result.MaxECParityCount); + Assert.Equal(5242880u, result.MaxObjectSize); + Assert.Equal(8000u, result.MsPerBlock); + Assert.Equal(100_000_000u, result.WithdrawFee); + } + + [Fact] + public async void NodeInfoCallbackAndInterceptorTest() + { + bool callbackInvoked = false; + bool interceptorInvoked = false; + + var options = ClientOptions; + options.Value.Callback = (cs) => + { + callbackInvoked = true; + Assert.True(cs.ElapsedMicroSeconds > 0); + }; + + 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(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.NotNull(result.Addresses); + Assert.True(result.Attributes.Count > 0); + } +} diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs new file mode 100644 index 0000000..98e32f7 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -0,0 +1,337 @@ +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; +using FrostFS.SDK.Cryptography; +using Xunit.Abstractions; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase +{ + private readonly ITestOutputHelper _testOutputHelper = testOutputHelper; + + const string clientCut = "clientCut"; + const string serverCut = "serverCut"; + const string singleObject = "singleObject"; + + [Theory] + [InlineData(true, 1, 1)] + [InlineData(false, 1, 1)] + [InlineData(true, 1, 3)] + [InlineData(false, 1, 3)] + [InlineData(true, 2, 3)] + [InlineData(false, 2, 3)] + [InlineData(true, 2, 1)] + [InlineData(false, 2, 1)] + public async void FullScenario(bool unique, uint backupFactor, int replicas) + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + _testOutputHelper.WriteLine("client created"); + + await Cleanup(client); + _testOutputHelper.WriteLine("existing containers removed"); + + FrostFsContainerId containerId = await CreateContainer(client, + ctx: default, + token: null, + unique: unique, + backupFactor: backupFactor, + selectors: [], + filter: [], + containerAttributes: [], + new FrostFsReplica(replicas)); + + Assert.NotNull(containerId); + _testOutputHelper.WriteLine("container created"); + + await AddObjectRules(client, containerId); + _testOutputHelper.WriteLine("rules added"); + + await RunSuite(client, containerId); + } + + private async Task RunSuite(IFrostFSClient client, FrostFsContainerId containerId) + { + int[] objectSizes = [1, 257, 6 * 1024, 20 * 1024]; + + string[] objectTypes = [clientCut, serverCut, singleObject]; + + foreach (var objectSize in objectSizes) + { + _testOutputHelper.WriteLine($"test set for object size {objectSize}"); + + var bytes = GetRandomBytes(objectSize); + var hash = SHA256.HashData(bytes); + + FrostFsObjectId objectId; + foreach (var type in objectTypes) + { + switch (type) + { + case serverCut: + objectId = await CreateObjectServerCut(client, containerId, bytes); + break; + case clientCut: + objectId = await CreateObjectClientCut(client, containerId, bytes); + break; + case singleObject: + if (objectSize > 1 * 1024 * 1024) + continue; + objectId = await PutSingleObject(client, containerId, bytes); + + break; + default: + throw new ArgumentException("unexpected object type"); + } + + Assert.NotNull(objectId); + + _testOutputHelper.WriteLine($"\tobject created"); + + await ValidateContent(client, containerId, hash, objectId); + _testOutputHelper.WriteLine($"\tcontent validated"); + + await ValidateFilters(client, containerId, objectId, null, (ulong)bytes.Length); + _testOutputHelper.WriteLine($"\tfilters validated"); + + // if (type != clientCut) + // { + // await ValidatePatch(client, containerId, bytes, objectId); + // _testOutputHelper.WriteLine($"\tpatch validated"); + // } + + await ValidateRange(client, containerId, bytes, objectId); + _testOutputHelper.WriteLine($"\trange validated"); + + await ValidateRangeHash(client, containerId, bytes, objectId); + _testOutputHelper.WriteLine($"\trange hash validated"); + + await RemoveObject(client, containerId, objectId); + _testOutputHelper.WriteLine($"\tobject removed"); + } + } + } + + private static async Task ValidateRangeHash(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) + { + if (bytes.Length < 200) + return; + + var rangeParam = new PrmRangeHashGet(containerId, objectId, [new FrostFsRange(100, 64)], bytes); + + var hashes = await client.GetRangeHashAsync(rangeParam, default); + + foreach (var h in hashes) + { + var x = h[..32].ToArray(); + Assert.NotNull(x); + Assert.True(x.Length > 0); + } + } + + private async Task ValidateRange(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) + { + if (bytes.Length < 200) + return; + + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(0, 50)); + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(50, 100)); + + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange((ulong)bytes.Length-100, 100)); + + if (bytes.Length >= 6200) + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(6000, 100)); + } + + private async Task CheckRange(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId, FrostFsRange range) + { + var rangeParam = new PrmRangeGet(containerId, objectId, range); + + var rangeReader = await client.GetRangeAsync(rangeParam, default); + + var rangeBytes = new byte[rangeParam.Range.Length]; + MemoryStream ms = new(rangeBytes); + + ReadOnlyMemory? chunk; + while ((chunk = await rangeReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice((int)range.Offset, (int)range.Length)), SHA256.HashData(rangeBytes)); + + _testOutputHelper.WriteLine($"\t\trange {range.Offset};{range.Length} validated"); + } + + private static async Task ValidatePatch(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) + { + if (bytes.Length < 1024 + 64) + return; + + var patch = new byte[1024]; + for (int i = 0; i < patch.Length; i++) + { + patch[i] = 32; + } + + var range = new FrostFsRange(64, (ulong)patch.Length); + + var patchParams = new PrmObjectPatch( + new FrostFsAddress(containerId, objectId), + payload: new MemoryStream(patch), + maxChunkLength: 1024, + range: range); + + var newIbjId = await client.PatchObjectAsync(patchParams, default); + + var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, newIbjId), default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + for (int i = 0; i < (int)range.Offset; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + + var rangeEnd = range.Offset + range.Length; + + for (int i = (int)range.Offset; i < (int)rangeEnd; i++) + Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); + + for (int i = (int)rangeEnd; i < bytes.Length; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + } + + + private async Task ValidateFilters(IFrostFSClient client, FrostFsContainerId containerId, FrostFsObjectId objectId, SplitId? splitId, ulong length) + { + var ecdsaKey = keyString.LoadWif(); + + var networkInfo = await client.GetNetmapSnapshotAsync(default); + + await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); + + await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); + + if (splitId != null) + { + await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, splitId)); + } + + await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); + + await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); + + await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); + + await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); + + await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, length)); + + // var checkSum = CheckSum.CreateCheckSum(hash); + // await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); + + await CheckFilter(client, containerId, new FilterByPhysicallyStored()); + } + + private static async Task RemoveObject(IFrostFSClient client, FrostFsContainerId containerId, FrostFsObjectId objectId) + { + await client.DeleteObjectAsync(new PrmObjectDelete(containerId, objectId), default); + + try + { + _ = await client.GetObjectAsync( + new PrmObjectGet(containerId, objectId), + default); + + Assert.Fail("Exception is expected here"); + } + catch (FrostFsResponseException ex) + { + Assert.Equal("object already removed", ex.Status!.Message); + } + } + + private static async Task ValidateContent(IFrostFSClient client, FrostFsContainerId containerId, byte[] hash, FrostFsObjectId objectId) + { + var @object = await client.GetObjectAsync( + new PrmObjectGet(containerId, objectId), + default); + + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk = null; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + Assert.Equal(hash, SHA256.HashData(downloadedBytes)); + } + + private static async Task CreateObjectServerCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) + { + var header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]); + + var param = new PrmObjectPut(header); + + var objectWriter = await client.PutObjectAsync(param, default).ConfigureAwait(true); + + await objectWriter.WriteAsync(bytes); + return await objectWriter.CompleteAsync(); + } + + private static async Task CreateObjectClientCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) + { + var header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]); + + var param = new PrmObjectClientCutPut(header, payload: new MemoryStream(bytes)); + + return await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); + } + + private static async Task PutSingleObject(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) + { + var header = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + [new FrostFsAttributePair("fileName", "test")]); + + + var obj = new FrostFsObject(header) { SingleObjectPayload = bytes }; + + var param = new PrmSingleObjectPut(obj); + + return await client.PutSingleObjectAsync(param, default).ConfigureAwait(true); + } + + private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) + { + var resultObjectsCount = 0; + + PrmObjectSearch searchParam = new(containerId, null, [], filter); + + await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) + { + resultObjectsCount++; + var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); + } + + Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); + } +} diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/SessionTests/SessionTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/SessionTests/SessionTests.cs new file mode 100644 index 0000000..ab14d8e --- /dev/null +++ b/src/FrostFS.SDK.Tests/Smoke/Client/SessionTests/SessionTests.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using FrostFS.SDK.Client; + +namespace FrostFS.SDK.Tests.Smoke; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] +public class SessionTests : SmokeTestsBase +{ + [Fact] + public async void GetSessionTest() + { + var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); + + var token = await client.CreateSessionAsync(new(100), default); + + Assert.NotNull(token); + Assert.NotEqual(Guid.Empty, token.Id); + Assert.Equal(33, token.SessionKey.Length); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs similarity index 100% rename from src/FrostFS.SDK.Tests/Multithread/MultiThreadTestsBase.cs rename to src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs similarity index 92% rename from src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs rename to src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs index 4cb7e4d..194ef98 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadPoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs @@ -4,16 +4,12 @@ using System.Security.Cryptography; using FrostFS.SDK.Client; using FrostFS.SDK.Cryptography; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class MultithreadPoolSmokeTests : SmokeTestsBase { - private static readonly PrmWait lightWait = new(100, 1); - private InitParameters GetDefaultParams() { return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) @@ -170,7 +166,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["key1", "value1"]); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); @@ -219,7 +215,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; @@ -315,7 +311,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await pool.CreateContainerAsync(createContainerParam, default); + var createdContainer = await pool.PutContainerAsync(createContainerParam, default); var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -399,7 +395,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); - var container = await pool.CreateContainerAsync(createContainerParam, ctx); + var container = await pool.PutContainerAsync(createContainerParam, ctx); var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); @@ -482,7 +478,7 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); @@ -545,37 +541,4 @@ public class MultithreadPoolSmokeTests : SmokeTestsBase Assert.Fail($"Container {cid.GetValue()} exist"); } } - - private static byte[] GetRandomBytes(int size) - { - Random rnd = new(); - var bytes = new byte[size]; - rnd.NextBytes(bytes); - return bytes; - } - - private static IOptions GetSingleOwnerOptions(string key, string url) - { - return Options.Create(new ClientSettings - { - Key = key, - Host = url - }); - } - - private static IOptions GetOptions(string url) - { - return Options.Create(new ClientSettings - { - Host = url - }); - } - - static async Task Cleanup(Pool pool) - { - await foreach (var cid in pool.ListContainersAsync(default, default)) - { - await pool.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default).ConfigureAwait(true); - } - } } diff --git a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs similarity index 94% rename from src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs rename to src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs index b239e58..32aeb2e 100644 --- a/src/FrostFS.SDK.Tests/Multithread/MultithreadSmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs @@ -14,8 +14,6 @@ namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class MultithreadSmokeClientTests : SmokeTestsBase { - private static readonly PrmWait lightWait = new(100, 1); - [Fact] public async void AccountTest() { @@ -103,7 +101,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["key1", "value1"]); - var containerId = await client.CreateContainerAsync(createContainerParam, default); + var containerId = await client.PutContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); @@ -147,7 +145,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await client.CreateContainerAsync(createContainerParam, default); + var containerId = await client.PutContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; @@ -241,7 +239,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam, ctx); + var createdContainer = await client.PutContainerAsync(createContainerParam, ctx); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -312,7 +310,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -392,7 +390,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -446,7 +444,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await client.CreateContainerAsync(createContainerParam, default); + var createdContainer = await client.PutContainerAsync(createContainerParam, default); var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -502,7 +500,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); - var container = await client.CreateContainerAsync(createContainerParam, ctx); + var container = await client.PutContainerAsync(createContainerParam, ctx); var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); @@ -583,7 +581,7 @@ public class MultithreadSmokeClientTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await client.CreateContainerAsync(createContainerParam, default); + var containerId = await client.PutContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); @@ -685,30 +683,4 @@ public class MultithreadSmokeClientTests : SmokeTestsBase Assert.Single(result.Addresses); Assert.Equal(9, result.Attributes.Count); } - - private static byte[] GetRandomBytes(int size) - { - Random rnd = new(); - var bytes = new byte[size]; - rnd.NextBytes(bytes); - return bytes; - } - - private static IOptions GetClientOptions(string key, string url) - { - return Options.Create(new ClientSettings - { - Key = key, - Host = url - }); - } - - - static async Task Cleanup(IFrostFSClient client) - { - await foreach (var cid in client.ListContainersAsync(default, default)) - { - await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default); - } - } } diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs similarity index 95% rename from src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs rename to src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs index 94b2c8b..9e7ea80 100644 --- a/src/FrostFS.SDK.Tests/Smoke/PoolSmokeTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs @@ -10,8 +10,6 @@ namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public class PoolSmokeTests : SmokeTestsBase { - private static readonly PrmWait lightWait = new(100, 1); - private InitParameters GetDefaultParams() { return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) @@ -168,7 +166,7 @@ public class PoolSmokeTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["key1", "value1"]); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var bytes = GetRandomBytes(1024); @@ -217,7 +215,7 @@ public class PoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var bytes = new byte[] { 1, 2, 3 }; @@ -315,7 +313,7 @@ public class PoolSmokeTests : SmokeTestsBase PrmWait.DefaultParams, xheaders: ["testKey", "testValue"]); - var createdContainer = await pool.CreateContainerAsync(createContainerParam, ctx); + var createdContainer = await pool.PutContainerAsync(createContainerParam, ctx); var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); Assert.NotNull(container); @@ -399,7 +397,7 @@ public class PoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), PrmWait.DefaultParams); - var container = await pool.CreateContainerAsync(createContainerParam, ctx); + var container = await pool.PutContainerAsync(createContainerParam, ctx); var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); Assert.NotNull(containerInfo); @@ -483,7 +481,7 @@ public class PoolSmokeTests : SmokeTestsBase new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), lightWait); - var containerId = await pool.CreateContainerAsync(createContainerParam, default); + var containerId = await pool.PutContainerAsync(createContainerParam, default); var ctx = new CallContext(TimeSpan.FromSeconds(10)); @@ -545,20 +543,4 @@ public class PoolSmokeTests : SmokeTestsBase Assert.Fail($"Container {cid.GetValue()} exist"); } } - - private static byte[] GetRandomBytes(int size) - { - Random rnd = new(); - var bytes = new byte[size]; - rnd.NextBytes(bytes); - return bytes; - } - - static async Task Cleanup(Pool pool) - { - await foreach (var cid in pool.ListContainersAsync(default, default)) - { - await pool.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default).ConfigureAwait(true); - } - } } diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs deleted file mode 100644 index 3dac829..0000000 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeClientTests.cs +++ /dev/null @@ -1,829 +0,0 @@ -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 FrostFS.Session; -using Google.Protobuf; -using Microsoft.Extensions.Options; - -namespace FrostFS.SDK.Tests.Smoke; - -[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class SmokeClientTests : SmokeTestsBase -{ - private static readonly PrmWait lightWait = new(100, 1); - - [Fact] - public async void AccountTest() - { - var test = lightWait.Timeout; - - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var result = await client.GetBalanceAsync(default); - - Assert.NotNull(result); - Assert.True(result.Value == 0); - } - - [Fact] - public async void NodeInfoTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var result = await client.GetNodeInfoAsync(default); - - 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); - } - - [Fact] - public async void NodeInfoStatisticsTest() - { - var options = ClientOptions; - - var callbackContent = string.Empty; - options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - var result = await client.GetNodeInfoAsync(default); - - Assert.NotEmpty(callbackContent); - } - - [Fact] - public async void GetSessionTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var token = await client.CreateSessionAsync(new(100), default); - - Assert.NotNull(token); - Assert.NotEqual(Guid.Empty, token.Id); - Assert.Equal(33, token.SessionKey.Length); - } - - [Fact] - public async void CreateObjectWithSessionToken() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [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( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) - .ConfigureAwait(true); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client).ConfigureAwait(true); - } - - [Fact] - public async void FilterTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [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 param = new PrmObjectPut( - 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 ecdsaKey = keyString.LoadWif(); - - var networkInfo = await client.GetNetmapSnapshotAsync(default); - - await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); - - await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - - await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); - - await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); - - await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); - - await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - - await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - - await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); - - var checkSum = CheckSum.CreateCheckSum(bytes); - - // await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); - - await CheckFilter(client, containerId, new FilterByPhysicallyStored()); - } - - private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) - { - var resultObjectsCount = 0; - - PrmObjectSearch searchParam = new(containerId, null, [], filter); - - await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) - { - resultObjectsCount++; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); - } - - Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioTest(int objectSize) - { - bool callbackInvoked = false; - - var options = ClientOptions; - - options.Value.Callback = new((cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }); - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - await Cleanup(client); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var containerId = await client.CreateContainerAsync(createContainerParam, ctx); - - 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: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - 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 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 res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - } - - [Theory] - [InlineData(1)] - [InlineData(500 * 1024)] - [InlineData(3 * 1024 * 1024)] - public async void PutSingleObjectTest(int objectSize) - { - var options = ClientOptions; - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - await Cleanup(client); - - var ctx = new CallContext(); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams, - xheaders: ["testKey1", "testValue1"]); - - var containerId = await client.CreateContainerAsync(createContainerParam, ctx); - - var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); - Assert.NotNull(container); - - await AddObjectRules(client, containerId); - - var bytes = GetRandomBytes(objectSize); - - var header = new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName1", "test1")]); - - var obj = new FrostFsObject(header) { SingleObjectPayload = bytes }; - - var param = new PrmSingleObjectPut(obj); - - var objectId = await client.PutSingleObjectAsync(param, default).ConfigureAwait(true); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName1", "test1"); - - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) - { - hasObject = true; - - var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName1", objHeader.Attributes.First().Key); - Assert.Equal("test1", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void CleanupTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - } - - [Fact] - public async void PatchTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var containerId = await client.CreateContainerAsync(createContainerParam, 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++) - { - bytes[i] = 31; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var patch = new byte[256]; - for (int i = 0; i < patch.Length; i++) - { - patch[i] = 32; - } - - var range = new FrostFsRange(64, (ulong)patch.Length); - - var patchParams = new PrmObjectPatch( - 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(containerId, newIbjId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - for (int i = 0; i < (int)range.Offset; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); - - var rangeEnd = range.Offset + range.Length; - - for (int i = (int)range.Offset; i < (int)rangeEnd; i++) - Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); - - for (int i = (int)rangeEnd; i < bytes.Length; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void RangeTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var containerId = await client.CreateContainerAsync(createContainerParam, 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++) - { - bytes[i] = (byte)i; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular)); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var rangeParam = new PrmRangeGet(containerId, objectId, new FrostFsRange(50, 100)); - - var rangeReader = await client.GetRangeAsync(rangeParam, default); - - var downloadedBytes = new byte[rangeParam.Range.Length]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk; - while ((chunk = await rangeReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(50, 100)), SHA256.HashData(downloadedBytes)); - } - - [Fact] - public async void RangeHashTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var containerId = await client.CreateContainerAsync(createContainerParam, 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++) - { - bytes[i] = (byte)i; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular)); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var rangeParam = new PrmRangeHashGet(containerId, objectId, [new FrostFsRange(100, 64)], bytes); - - var hashes = await client.GetRangeHashAsync(rangeParam, default); - - foreach (var hash in hashes) - { - var x = hash[..32].ToArray(); - Assert.NotNull(x); - Assert.True(x.Length > 0); - } - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioWithSessionTest(int objectSize) - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - await Cleanup(client); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(3)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - PrmWait.DefaultParams); - - var container = await client.CreateContainerAsync(createContainerParam, ctx); - - var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); - Assert.NotNull(containerInfo); - - await AddObjectRules(client, container); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: container, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default)) - { - hasObject = true; - - var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void ClientCutScenarioTest() - { - var options = ClientOptions; - options.Value.Interceptors.Add(new CallbackInterceptor()); - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - await Cleanup(client); - - int[] replicas = [3]; //1 - - foreach (var rep in replicas) - { - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo( - new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(rep)), - [new FrostFsAttributePair("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")]), - lightWait); - - var containerId = await client.CreateContainerAsync(createContainerParam, default); - - var ctx = new CallContext(TimeSpan.FromSeconds(10)); - - var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); - - Assert.NotNull(container); - - await AddObjectRules(client, containerId); - - var mb = 1024 * 1024; - - int[] objectSizes = [1, 1024, 64 * mb + 1]; //64 * mb, 64 * mb - 1, , 2 * 64 * mb + 256]; - - foreach (var objectSize in objectSizes) - { - byte[] bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectClientCutPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes)); - - var objectId = await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); - - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - } - } - } - - [Fact] - public async void NodeInfoCallbackAndInterceptorTest() - { - bool callbackInvoked = false; - bool interceptorInvoked = false; - - var options = ClientOptions; - options.Value.Callback = (cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }; - - 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(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.NotNull(result.Addresses); - Assert.True(result.Attributes.Count > 0); - } - - private static byte[] GetRandomBytes(int size) - { - Random rnd = new(); - var bytes = new byte[size]; - rnd.NextBytes(bytes); - return bytes; - } - - private static IOptions GetClientOptions(string key, string url) - { - return Options.Create(new ClientSettings - { - Key = key, - Host = url - }); - } - - static async Task Cleanup(IFrostFSClient client) - { - await foreach (var cid in client.ListContainersAsync(default, default)) - { - await client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default); - } - } - - private static async Task AddContainerRules(IFrostFSClient client, FrostFsContainerId containerId) - { - var addChainPrm = new PrmApeChainAdd( - new FrostFsChainTarget(FrostFsTargetType.Container, containerId.GetValue()), - new FrostFsChain - { - ID = Encoding.ASCII.GetBytes("chain-id-test1"), - Rules = [ - new FrostFsRule - { - Status = RuleStatus.Allow, - Actions = new Actions(inverted: false, names: ["*"]), - Resources = new Resource (inverted: false, names: [$"native:container/*"]), - 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); - } - - 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); - - await Task.Delay(8000); - } -} diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index a524a82..ac76a54 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -1,28 +1,34 @@ -using System.Security.Cryptography; - +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Security.Cryptography; +using System.Security.Principal; +using System.Text; using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using Grpc.Core; using Microsoft.Extensions.Options; +using Xunit.Abstractions; namespace FrostFS.SDK.Tests.Smoke; +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public abstract class SmokeTestsBase { // cluster Ori - internal readonly string url = "http://10.78.128.207:8080"; - internal readonly string keyString = "L4JWLdedUd4b21sriRHtCPGkjG2Mryz2AWLiVqTBSNyxxyAUcc7s"; - + // internal readonly string url = "http://10.78.128.207:8080"; + // internal readonly string keyString = "L4JWLdedUd4b21sriRHtCPGkjG2Mryz2AWLiVqTBSNyxxyAUcc7s"; // cluster - // internal readonly string url = "http://10.78.128.190:8080"; - // internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; + internal readonly string url = "http://10.78.128.190:8080"; + internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; // WSL2 - //internal readonly string url = "http://172.29.238.97:8080"; - //internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; + // internal readonly string url = "http://172.29.238.97:8080"; + // internal readonly string keyString = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; // "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; //"KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; @@ -44,4 +50,88 @@ public abstract class SmokeTestsBase protected static Func GrpcChannel => (url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url)); protected IOptions ClientOptions => Options.Create(new ClientSettings { Key = keyString, Host = url }); + + protected static readonly PrmWait lightWait = new(100, 1); + + protected static byte[] GetRandomBytes(int size) + { + Random rnd = new(); + var bytes = new byte[size]; + rnd.NextBytes(bytes); + return bytes; + } + + protected static async Task CreateContainer(IFrostFSClient client, + CallContext ctx, + FrostFsSessionToken? token, + bool unique, + uint backupFactor, + Collection selectors, + Collection filter, + Collection containerAttributes, + params FrostFsReplica[] replicas) + { + ArgumentNullException.ThrowIfNull(client); + + var networkSettings = await client.GetNetworkSettingsAsync(ctx); + + var attributes = containerAttributes ?? []; + + if (networkSettings.HomomorphicHashingDisabled) + attributes.Add(new("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")); + + var containerInfo = new FrostFsContainerInfo( + new FrostFsPlacementPolicy(unique, backupFactor, selectors, filter, replicas), + [.. attributes]); + + var createContainerParam = new PrmContainerCreate( + containerInfo, + PrmWait.DefaultParams, + token, + xheaders: ["key1", "value1"]); + + return await client.PutContainerAsync(createContainerParam, ctx); + } + + protected static async Task Cleanup(IFrostFSClient client) + { + ArgumentNullException.ThrowIfNull(client); + + var tasks = new List(); + await foreach (var cid in client.ListContainersAsync(default, default)) + { + tasks.Add(client.DeleteContainerAsync(new PrmContainerDelete(cid, lightWait), default)); + } + + await Task.WhenAll(tasks); + } + + protected static async Task AddObjectRules(IFrostFSClient client, FrostFsContainerId containerId) + { + ArgumentNullException.ThrowIfNull(client); + ArgumentNullException.ThrowIfNull(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); + + await Task.Delay(8000); + } } diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs index 2088b4b..db1e193 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs @@ -16,7 +16,7 @@ public class ContainerTest : ContainerTestsBase { var param = new PrmContainerCreate(new FrostFsContainerInfo(Mocker.PlacementPolicy), PrmWait.DefaultParams); - var result = await GetClient().CreateContainerAsync(param, default); + var result = await GetClient().PutContainerAsync(param, default); Assert.NotNull(result); Assert.NotNull(result.GetValue()); From 8835b23ed3fcb172c3f4d1d068e6142d53223137 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 27 Feb 2025 17:35:19 +0300 Subject: [PATCH 43/65] [#34] Client: Add rules deserialization Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/ApeRules/Actions.cs | 2 +- .../ApeRules/FrostFsRule.cs | 2 +- src/FrostFS.SDK.Client/ApeRules/Resources.cs | 12 +- .../ApeRules/RuleSerializer.cs | 243 +++++++++++++++++- src/FrostFS.SDK.Client/FrostFSClient.cs | 4 +- .../Interfaces/IFrostFSClient.cs | 4 +- .../Models/Session/FrostFsSessionToken.cs | 1 - .../Parameters/PrmApeChainAdd.cs | 4 +- .../Parameters/PrmApeChainList.cs | 4 +- .../Parameters/PrmApeChainRemove.cs | 5 +- src/FrostFS.SDK.Client/Pool/Pool.cs | 4 +- .../Services/ApeManagerServiceProvider.cs | 7 +- .../Smoke/Client/MiscTests/InterceptorTest.cs | 2 + .../Smoke/Client/ObjectTests/ObjectTests.cs | 5 + .../MultithreadSmokeClientTests.cs | 2 - src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 4 +- src/FrostFS.SDK.Tests/Unit/ApeTests.cs | 167 ++++++++++++ src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 6 +- 18 files changed, 426 insertions(+), 52 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/Unit/ApeTests.cs diff --git a/src/FrostFS.SDK.Client/ApeRules/Actions.cs b/src/FrostFS.SDK.Client/ApeRules/Actions.cs index 28600b8..71888b9 100644 --- a/src/FrostFS.SDK.Client/ApeRules/Actions.cs +++ b/src/FrostFS.SDK.Client/ApeRules/Actions.cs @@ -6,7 +6,7 @@ public struct Actions(bool inverted, string[] names) : System.IEquatable +public struct Resources(bool inverted, string[] names) : System.IEquatable { public bool Inverted { get; set; } = inverted; @@ -8,10 +8,10 @@ public struct Resource(bool inverted, string[] names) : System.IEquatable(byte[] buf, int offset, T[] slice, Func marshalT) + private static int SliceMarshal(byte[] buf, int offset, T[] slice, Func marshalT) { if (slice == null) { @@ -269,7 +305,7 @@ internal static class RuleSerializer return SliceMarshal(buf, offset, rule.Conditions!, MarshalCondition); } - private static int MarshalResources(byte[] buf, int offset, Resource resources) + private static int MarshalResources(byte[] buf, int offset, Resources resources) { offset = BoolMarshal(buf, offset, resources.Inverted); @@ -283,4 +319,187 @@ internal static class RuleSerializer throw new FrostFsException("actual data size differs from expected"); } } + + private static (int, bool) BoolUnmarshal(byte[] buf, int offset) + { + (offset, byte val) = UInt8Unmarshal(buf, offset); + return (offset, val == ByteTrue); + } + + private static (int, string) StringUnmarshal(byte[] buf, int offset) + { + (offset, long size) = Int64Unmarshal(buf, offset); + + if (size == 0) + { + return (offset, string.Empty); + } + + if (size > MaxSliceLen) + { + throw new FrostFsException($"string is too long: '{size}'"); + } + if (size < 0) + { + throw new FrostFsException($"invalid string size: '{size}'"); + } + + if (buf.Length - offset < size) + { + throw new FrostFsException($"not enough bytes left to string value"); + } + + return (offset + (int)size, System.Text.Encoding.UTF8.GetString(buf, offset, (int)size)); + } + + private static (int, Actions) UnmarshalActions(byte[] buf, int offset) + { + Actions action = new(); + (offset, action.Inverted) = BoolUnmarshal(buf, offset); + + (offset, action.Names) = SliceUnmarshal(buf, offset, StringUnmarshal); + + return (offset, action); + } + + private static (int, Resources) UnmarshalResources(byte[] buf, int offset) + { + Resources res = new(); + + (offset, res.Inverted) = BoolUnmarshal(buf, offset); + (offset, res.Names) = SliceUnmarshal(buf, offset, StringUnmarshal); + + return (offset, res); + } + + private static (int, Condition) UnmarshalCondition(byte[] buf, int offset) + { + Condition cond = new(); + (offset, var op) = UInt8Unmarshal(buf, offset); + + cond.Op = (ConditionType)op; + + (offset, var kind) = UInt8Unmarshal(buf, offset); + + cond.Kind = (ConditionKindType)kind; + + (offset, cond.Key) = StringUnmarshal(buf, offset); + + (offset, cond.Value) = StringUnmarshal(buf, offset); + + return (offset, cond); + } + + private static (int, FrostFsRule) UnmarshalRule(byte[] buf, int offset) + { + FrostFsRule rule = new(); + + (offset, byte statusV) = UInt8Unmarshal(buf, offset); + rule.Status = (RuleStatus)statusV; + + (offset, rule.Actions) = UnmarshalActions(buf, offset); + + (offset, rule.Resources) = UnmarshalResources(buf, offset); + + (offset, rule.Any) = BoolUnmarshal(buf, offset); + + (offset, rule.Conditions) = SliceUnmarshal(buf, offset, UnmarshalCondition); + + return (offset, rule); + } + + private static (int, byte) UInt8Unmarshal(byte[] buf, int offset) + { + if (buf.Length - offset < 1) + { + throw new FrostFsException($"not enough bytes left to read a value of type 'byte' from offset {offset}"); + } + + return (offset + 1, buf[offset]); + } + + private static (int, long) Int64Unmarshal(byte[] buf, int offset) + { + if (buf.Length - offset < sizeof(long)) + { + throw new FrostFsException($"not enough bytes left to read a value of type 'long' from offset {offset}"); + } + + return Varint(buf, offset); + } + + private static (int, T[]) SliceUnmarshal(byte[] buf, int offset, Func unmarshalT) + { + var (newOffset, size) = Varint(buf, offset); + + if (size == NullSlice) + { + return (newOffset, []); + } + + if (size > MaxSliceLen) + { + throw new FrostFsException($"slice size is too big: '{size}'"); + } + + if (size < 0) + { + throw new FrostFsException($"invalid slice size: '{size}'"); + } + + var result = new T[size]; + for (int i = 0; i < result.Length; i++) + { + (newOffset, result[i]) = unmarshalT(buf, newOffset); + } + + return (newOffset, result); + } + + private static void VerifyUnmarshal(byte[] buf, int offset) + { + if (buf.Length != offset) + { + throw new FrostFsException("unmarshalled bytes left"); + } + } + + private static int MaxVarIntLen64 = 10; + + public static (int, long) Varint(byte[] buf, int offset) + { + var (ux, n) = Uvarint(buf, offset); // ok to continue in presence of error + long x = (long)ux >> 1; + if ((ux & 1) != 0) + { + x = ~x; + } + return (n, x); + } + + public static (ulong, int) Uvarint(byte[] buf, int offset) + { + ulong x = 0; + int s = 0; + + for (int i = offset; i < buf.Length; i++) + { + byte b = buf[i]; + if (i == MaxVarIntLen64) + { + return (0, -(i + 1)); // overflow + } + if (b < 0x80) + { + if (i == MaxVarIntLen64 - 1 && b > 1) + { + return (0, -(i + 1)); // overflow + } + return (x | ((ulong)b << s), i + 1); + } + x |= (ulong)(b & 0x7f) << s; + s += 7; + } + return (0, 0); + } } diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 83f917e..f72265c 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Frostfs.V2.Ape; - using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Client.Services; using FrostFS.SDK.Cryptography; @@ -195,7 +193,7 @@ public class FrostFSClient : IFrostFSClient return GetApeManagerService().RemoveChainAsync(args, ctx); } - public Task ListChainAsync(PrmApeChainList args, CallContext ctx) + public Task ListChainAsync(PrmApeChainList args, CallContext ctx) { return GetApeManagerService().ListChainAsync(args, ctx); } diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index 594cc63..d546156 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Frostfs.V2.Ape; - namespace FrostFS.SDK.Client.Interfaces; public interface IFrostFSClient @@ -25,7 +23,7 @@ public interface IFrostFSClient Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx); - Task ListChainAsync(PrmApeChainList args, CallContext ctx); + Task ListChainAsync(PrmApeChainList args, CallContext ctx); #endregion #region Container diff --git a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs index 5f675ae..708c9f6 100644 --- a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs +++ b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using FrostFS.Refs; using FrostFS.SDK.Client; diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs index 54b5171..adcc1b9 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainAdd.cs @@ -1,6 +1,4 @@ -using FrostFS.SDK.Client; - -namespace FrostFS.SDK.Client; +namespace FrostFS.SDK.Client; public readonly struct PrmApeChainAdd(FrostFsChainTarget target, FrostFsChain chain, string[]? xheaders = null) : System.IEquatable { diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs index 57c90bd..a946840 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainList.cs @@ -1,6 +1,4 @@ -using FrostFS.SDK.Client; - -namespace FrostFS.SDK.Client; +namespace FrostFS.SDK.Client; public readonly struct PrmApeChainList(FrostFsChainTarget target, string[]? xheaders = null) : System.IEquatable { diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs index 1fc110b..720c3c4 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs @@ -1,7 +1,4 @@ -using System; -using FrostFS.SDK.Client; - -namespace FrostFS.SDK.Client; +namespace FrostFS.SDK.Client; public readonly struct PrmApeChainRemove( FrostFsChainTarget target, diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs index f0b7a6e..cd09d30 100644 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ b/src/FrostFS.SDK.Client/Pool/Pool.cs @@ -5,8 +5,6 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -using Frostfs.V2.Ape; - using FrostFS.Refs; using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Client.Mappers.GRPC; @@ -550,7 +548,7 @@ public partial class Pool : IFrostFSClient await client.Client!.RemoveChainAsync(args, ctx).ConfigureAwait(false); } - public async Task ListChainAsync(PrmApeChainList args, CallContext ctx) + public async Task ListChainAsync(PrmApeChainList args, CallContext ctx) { var client = Connection(); return await client.Client!.ListChainAsync(args, ctx).ConfigureAwait(false); diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 174d460..5331b80 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -1,7 +1,6 @@ using System; +using System.Linq; using System.Threading.Tasks; - -using Frostfs.V2.Ape; using Frostfs.V2.Apemanager; using Google.Protobuf; @@ -61,7 +60,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor Verifier.CheckResponse(response); } - internal async Task ListChainAsync(PrmApeChainList args, CallContext ctx) + internal async Task ListChainAsync(PrmApeChainList args, CallContext ctx) { ListChainsRequest request = new() { @@ -78,6 +77,6 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor Verifier.CheckResponse(response); - return [.. response.Body.Chains]; + return [.. response.Body.Chains.Select(c => RuleSerializer.Deserialize([.. c.Raw]))]; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs index c3df8e5..664d522 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs @@ -1,8 +1,10 @@ +using System.Diagnostics.CodeAnalysis; using FrostFS.SDK.Client; using FrostFS.SDK.SmokeTests; namespace FrostFS.SDK.Tests.Smoke; +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class InterceptorTests() : SmokeTestsBase { [Fact] diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs index 98e32f7..b6ae62b 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -124,11 +124,16 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase var hashes = await client.GetRangeHashAsync(rangeParam, default); + var objectRange = bytes.AsMemory().Slice(100, 64).ToArray(); + var expectedHash = SHA256.HashData(objectRange); + foreach (var h in hashes) { var x = h[..32].ToArray(); Assert.NotNull(x); Assert.True(x.Length > 0); + + // Assert.True(expectedHash.SequenceEqual(h.ToArray())); } } diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs index 32aeb2e..7de8155 100644 --- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs @@ -6,8 +6,6 @@ using FrostFS.SDK.Client.Interfaces; using FrostFS.SDK.Cryptography; using FrostFS.SDK.SmokeTests; -using Microsoft.Extensions.Options; - namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index ac76a54..59099a9 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; -using System.Security.Principal; using System.Text; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Interfaces; @@ -10,7 +9,6 @@ using FrostFS.SDK.Cryptography; using Grpc.Core; using Microsoft.Extensions.Options; -using Xunit.Abstractions; namespace FrostFS.SDK.Tests.Smoke; @@ -121,7 +119,7 @@ public abstract class SmokeTestsBase { Status = RuleStatus.Allow, Actions = new Actions(inverted: false, names: ["*"]), - Resources = new Resource (inverted: false, names: [$"native:object/*"]), + Resources = new Resources (inverted: false, names: [$"native:object/*"]), Any = false, Conditions = [] } diff --git a/src/FrostFS.SDK.Tests/Unit/ApeTests.cs b/src/FrostFS.SDK.Tests/Unit/ApeTests.cs new file mode 100644 index 0000000..dea17fb --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/ApeTests.cs @@ -0,0 +1,167 @@ +using System.Text; +using FrostFS.SDK.Client; + +namespace FrostFS.SDK.Tests.Unit; + +public class ApeTests : ContainerTestsBase +{ + [Fact] + public void ApeRule1Test() + { + var chain = new FrostFsChain + { + ID = Encoding.ASCII.GetBytes("chain-id-test"), + Rules = [ + new FrostFsRule + { + Status = RuleStatus.Allow, + Actions = new Actions(inverted: false, names: ["*"]), + Resources = new Resources (inverted: false, names: [$"native:object/*"]), + Any = false, + Conditions = [] + } + ], + MatchType = RuleMatchType.DenyPriority + }; + + var serialized = RuleSerializer.Serialize(chain); + var restoredChain = RuleSerializer.Deserialize(serialized); + + Assert.True(chain.ID.SequenceEqual(restoredChain.ID)); + + Assert.Equal(chain.MatchType, restoredChain.MatchType); + CompareRules(chain.Rules, restoredChain.Rules); + } + + [Fact] + public void ApeRule2Test() + { + var chain = new FrostFsChain + { + ID = Encoding.ASCII.GetBytes("dumptext"), + Rules = [ + new FrostFsRule + { + Status = RuleStatus.AccessDenied, + Actions = new Actions(inverted: true, names: ["put,get"]), + Resources = new Resources (inverted: true, names: [$"native:object/*,blablabla"]), + Any = true, + Conditions = [ + new () { + Key = "key", + Value = "value", + Kind = ConditionKindType.Resource, + Op = ConditionType.CondStringEquals + }, + new () { + Key = "key1", + Value = "value1", + Kind = ConditionKindType.Request, + Op = ConditionType.CondNumericGreaterThan + } + ] + } + ], + MatchType = RuleMatchType.FirstMatch + }; + + var serialized = RuleSerializer.Serialize(chain); + var restoredChain = RuleSerializer.Deserialize(serialized); + + Assert.True(chain.ID.SequenceEqual(restoredChain.ID)); + + Assert.Equal(chain.MatchType, restoredChain.MatchType); + CompareRules(chain.Rules, restoredChain.Rules); + } + + [Fact] + public void NegativeDeserialize1Test() + { + try + { + _ = RuleSerializer.Deserialize(null); + Assert.Fail("Error is expected"); + } + catch (ArgumentNullException) + { + } + } + + [Fact] + public void NegativeDeserialize2Test() + { + try + { + _ = RuleSerializer.Deserialize([]); + Assert.Fail("Error is expected"); + } + catch (FrostFsException) + { + } + } + + [Fact] + public void NegativeDeserialize3Test() + { + try + { + _ = RuleSerializer.Deserialize([1, 2, 3]); + Assert.Fail("Error is expected"); + } + catch (FrostFsException) + { + } + } + + [Fact] + public void NegativeDeserialize4Test() + { + try + { + //"\x00\x00:aws:iam::namespace:group/so\x82\x82\x82\x82\x82\x82u\x82" + _ = RuleSerializer.Deserialize([0x00, 0x00, 0x3A, 0x77, 0x73, 0x3A, 0x69, 0x61, 0x6D, 0x3A, 0x3A, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3A, 0x67, 0x72, 0x6F, 0x75, 0x70, 0x2F, 0x73, 0x6F, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x75, 0x82]); + Assert.Fail("Error is expected"); + } + catch (FrostFsException) + { + } + } + + private static void CompareRules(FrostFsRule[] rules1, FrostFsRule[] rules2) + { + Assert.NotNull(rules1); + Assert.NotNull(rules2); + + Assert.Equal(rules1.Length, rules2.Length); + + for (int ri = 0; ri < rules1.Length; ri++) + { + var rule1 = rules1[ri]; + var rule2 = rules2[ri]; + + Assert.Equal(rule1.Status, rule2.Status); + Assert.Equal(rule1.Any, rule2.Any); + + Assert.Equal(rule1.Actions, rule2.Actions); + + Assert.Equal(rule1.Resources, rule2.Resources); + + bool cond1Empty = rule1.Conditions == null || rule1.Conditions.Length == 0; + bool cond2Empty = rule2.Conditions == null || rule2.Conditions.Length == 0; + + if (cond1Empty && cond2Empty) + { + return; + } + + Assert.Equal(cond1Empty, cond2Empty); + + Assert.Equal(rule1.Conditions!.Length, rule2.Conditions!.Length); + + for (int i = 0; i < rule1.Conditions.Length; i++) + { + Assert.Equal(rule1.Conditions[i], rule2.Conditions[i]); + } + } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 445dd80..564627d 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -111,12 +111,12 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(header1.Split.SplitId); Assert.Null(header1.Split.Previous); - Assert.Equal(bytes[..blockSize], payload1); + Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(0, blockSize).ToArray()), SHA256.HashData(payload1)); Assert.True(header1.Attributes.Count == 0); Assert.Equal(header1.Split.SplitId, header2.Split.SplitId); Assert.Equal(objIds.ElementAt(0), header2.Split.Previous.Value); - Assert.Equal(bytes[blockSize..(blockSize * 2)], payload2); + Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(blockSize, blockSize).ToArray()), SHA256.HashData(payload2)); Assert.True(header2.Attributes.Count == 0); // last part @@ -124,7 +124,7 @@ public class ObjectTest : ObjectTestsBase Assert.NotNull(header3.Split.ParentHeader); Assert.NotNull(header3.Split.ParentSignature); Assert.Equal(header2.Split.SplitId, header3.Split.SplitId); - Assert.Equal(bytes[(fileLength / blockSize * blockSize)..fileLength], payload3); + Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(fileLength - fileLength % blockSize, fileLength % blockSize).ToArray()), SHA256.HashData(payload3)); Assert.True(header3.Attributes.Count == 0); //link object From 6988fcedae2570fc567fc5a0db4e7f75730afcf2 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 3 Mar 2025 16:18:44 +0300 Subject: [PATCH 44/65] [#35] Client: rollback to PutSingleObject for client cut upload Signed-off-by: Pavel Gross --- .../Services/ObjectServiceProvider.cs | 167 +++++++++++------- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 2 +- .../Smoke/Client/ObjectTests/ObjectTests.cs | 106 ++++++++--- 3 files changed, 187 insertions(+), 88 deletions(-) diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 1011b69..b5b5bde 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -385,89 +385,132 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return response.Body.ObjectId.ToModel(); } - + internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { - var payloadStream = args.Payload!; + var stream = args.Payload!; var header = args.Header!; if (header.PayloadLength > 0) args.PutObjectContext.FullLength = header.PayloadLength; - else if (payloadStream.CanSeek) - args.PutObjectContext.FullLength = (ulong)payloadStream.Length; + else if (stream.CanSeek) + args.PutObjectContext.FullLength = (ulong)stream.Length; else throw new ArgumentException("The stream does not have a length and payload length is not defined"); - if (args.PutObjectContext.MaxObjectSizeCache == 0) - { - var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx) - .ConfigureAwait(false); + if (args.PutObjectContext.FullLength == 0) + throw new ArgumentException("The stream has zero length"); - args.PutObjectContext.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; + var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); + args.PutObjectContext.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; + + var restBytes = args.PutObjectContext.FullLength; + + var objectSize = (int)Math.Min((ulong)args.PutObjectContext.MaxObjectSizeCache, restBytes); + + // define collection capacity + var objectsCount = (int)(restBytes / (ulong)objectSize) + ((restBytes % (ulong)objectSize) > 0 ? 1 : 0); + + // if the object fits one part, it can be loaded as non-complex object + if (objectsCount == 1) + { + var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); + return singlePartResult.ObjectId; } - var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition; - var objectSize = restBytes > 0 ? (int)Math.Min((ulong)args.PutObjectContext.MaxObjectSizeCache, restBytes) : args.PutObjectContext.MaxObjectSizeCache; + List parts = new(objectsCount); - //define collection capacity - var restPart = (restBytes % (ulong)objectSize) > 0 ? 1 : 0; - var objectsCount = args.PutObjectContext.FullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0; - - List sentObjectIds = new(objectsCount); - - FrostFsSplit? split = null; SplitId splitId = new(); + var partSize = args.PutObjectContext.MaxObjectSizeCache; + // keep attributes for the large object - var attributes = args.Header!.Attributes; - args.Header!.Attributes = null; + var attributes = args.Header!.Attributes.ToArray(); + header.Attributes = null; - // send all parts except the last one as separate Objects - while (restBytes > (ulong)args.PutObjectContext.MaxObjectSizeCache) + var remain = args.PutObjectContext.FullLength; + + FrostFsObjectHeader? parentHeader = null; + + var lastIndex = objectsCount - 1; + + bool rentBuffer = false; + byte[]? buffer = null; + + try { - split = new FrostFsSplit(splitId, sentObjectIds.LastOrDefault()); - - args.Header!.Split = split; - - var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); - - sentObjectIds.Add(result.ObjectId); - - restBytes -= (ulong)result.ObjectSize; - } - - // send the last part and create linkObject - if (sentObjectIds.Count > 0) - { - var largeObjectHeader = new FrostFsObjectHeader( - header.ContainerId, - FrostFsObjectType.Regular, - attributes != null ? [.. attributes] : []) + for (int i = 0; i < objectsCount; i++) { - PayloadLength = args.PutObjectContext.FullLength, - }; + if (args.CustomBuffer != null) + { + if (args.CustomBuffer.Length < partSize) + { + throw new ArgumentException($"Buffer size is too small. A buffer with capacity {partSize} is required"); + } - args.Header.Split!.ParentHeader = largeObjectHeader; + buffer = args.CustomBuffer; + } + else + { + buffer = ArrayPool.Shared.Rent(partSize); + rentBuffer = true; + } - var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); + var bytesToWrite = Math.Min((ulong)partSize, remain); - sentObjectIds.Add(result.ObjectId); + var size = await stream.ReadAsync(buffer, 0, (int)bytesToWrite).ConfigureAwait(false); - var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds); + if (i == lastIndex) + { + parentHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, attributes) + { + PayloadLength = args.PutObjectContext.FullLength + }; + } - _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false); + // Uploading the next part of the object. Note: the request must contain a non-null SplitId parameter + var partHeader = new FrostFsObjectHeader( + header.ContainerId, + FrostFsObjectType.Regular, + [], + new FrostFsSplit(splitId, parts.LastOrDefault(), + parentHeader: parentHeader)) + { + PayloadLength = (ulong)size + }; - var parentHeader = args.Header.GetHeader(); + var obj = new FrostFsObject(partHeader) + { + SingleObjectPayload = buffer.Length == size ? buffer : buffer[..size] + }; - return parentHeader.Split!.Parent.ToModel(); + var prm = new PrmSingleObjectPut(obj); + + var objId = await PutSingleObjectAsync(prm, ctx).ConfigureAwait(false); + + parts.Add(objId); + + if (i < lastIndex) + continue; + + // Once all parts of the object are uploaded, they must be linked into a single entity + var linkObject = new FrostFsLinkObject(header.ContainerId, splitId, parentHeader!, parts); + + _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false); + + // Retrieve the ID of the linked object + return partHeader.GetHeader().Split!.Parent.ToModel(); + } + + throw new FrostFsException("Unexpected error"); + } + finally + { + if (rentBuffer && buffer != null) + { + ArrayPool.Shared.Return(buffer); + } } - - // We are here if the payload is placed to one Object. It means no cut action, just simple PUT. - args.Header!.Attributes = attributes; - - var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); - - return singlePartResult.ObjectId; } struct PutObjectResult(FrostFsObjectId objectId, int objectSize) @@ -481,7 +524,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var payload = args.Payload!; var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; - var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition; chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize); @@ -526,22 +568,23 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl break; sentBytes += bytesCount; - + var chunkRequest = new PutRequest { Body = new PutRequest.Types.Body { - Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount) + Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory()[..bytesCount]) } }; chunkRequest.AddMetaHeader(args.XHeaders); - chunkRequest.Sign(ClientContext.Key.ECDsaKey); await stream.Write(chunkRequest).ConfigureAwait(false); } + args.PutObjectContext.CurrentStreamPosition += (ulong)sentBytes; + var response = await stream.Close().ConfigureAwait(false); Verifier.CheckResponse(response); @@ -580,10 +623,10 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var initRequest = new PutRequest { Body = new PutRequest.Types.Body - { + { Init = new PutRequest.Types.Body.Types.Init { - Header = grpcHeader + Header = grpcHeader, } } }; diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index 40cf1ad..ffc33a3 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -68,7 +68,7 @@ public static class ObjectTools { Header = grpcHeader, ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, - Payload = ByteString.CopyFrom(@object.SingleObjectPayload) + Payload = UnsafeByteOperations.UnsafeWrap(@object.SingleObjectPayload) }; obj.Signature = new Signature diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs index b6ae62b..81f0cd3 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -41,7 +41,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase backupFactor: backupFactor, selectors: [], filter: [], - containerAttributes: [], + containerAttributes: [new FrostFsAttributePair("contAttrKey", "contAttrValue")], new FrostFsReplica(replicas)); Assert.NotNull(containerId); @@ -49,7 +49,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase await AddObjectRules(client, containerId); _testOutputHelper.WriteLine("rules added"); - + await RunSuite(client, containerId); } @@ -73,15 +73,18 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase { case serverCut: objectId = await CreateObjectServerCut(client, containerId, bytes); + _testOutputHelper.WriteLine($"\tserver side cut"); break; case clientCut: objectId = await CreateObjectClientCut(client, containerId, bytes); + _testOutputHelper.WriteLine($"\tclient side cut"); break; - case singleObject: + case singleObject: if (objectSize > 1 * 1024 * 1024) continue; objectId = await PutSingleObject(client, containerId, bytes); - + _testOutputHelper.WriteLine($"\tput single object"); + break; default: throw new ArgumentException("unexpected object type"); @@ -91,17 +94,38 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase _testOutputHelper.WriteLine($"\tobject created"); + var ecdsaKey = ClientOptions.Value.Key.LoadWif(); + var owner = FrostFsOwner.FromKey(ecdsaKey); + + FrostFsHeaderResult expected = new() + { + HeaderInfo = new FrostFsObjectHeader( + containerId: containerId, + type: FrostFsObjectType.Regular, + attributes: [new FrostFsAttributePair("fileName", "test")], + split: null, + owner: owner, + version: new FrostFsVersion(2, 13)) + { + PayloadLength = (ulong)objectSize, + PayloadCheckSum = hash + } + }; + + await ValidateHeader(client, containerId, objectId, expected); + _testOutputHelper.WriteLine($"\theader validated"); + await ValidateContent(client, containerId, hash, objectId); _testOutputHelper.WriteLine($"\tcontent validated"); await ValidateFilters(client, containerId, objectId, null, (ulong)bytes.Length); _testOutputHelper.WriteLine($"\tfilters validated"); - // if (type != clientCut) - // { - // await ValidatePatch(client, containerId, bytes, objectId); - // _testOutputHelper.WriteLine($"\tpatch validated"); - // } + if (type != clientCut) + { + await ValidatePatch(client, containerId, bytes, objectId); + _testOutputHelper.WriteLine($"\tpatch validated"); + } await ValidateRange(client, containerId, bytes, objectId); _testOutputHelper.WriteLine($"\trange validated"); @@ -126,27 +150,27 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase var objectRange = bytes.AsMemory().Slice(100, 64).ToArray(); var expectedHash = SHA256.HashData(objectRange); - + foreach (var h in hashes) { var x = h[..32].ToArray(); Assert.NotNull(x); Assert.True(x.Length > 0); - // Assert.True(expectedHash.SequenceEqual(h.ToArray())); + // Assert.True(expectedHash.SequenceEqual(h.ToArray())); } } private async Task ValidateRange(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) { - if (bytes.Length < 200) + if (bytes.Length < 100) return; await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(0, 50)); - await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(50, 100)); + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(50, 50)); + + await CheckRange(client, containerId, bytes, objectId, new FrostFsRange((ulong)bytes.Length - 100, 100)); - await CheckRange(client, containerId, bytes, objectId, new FrostFsRange((ulong)bytes.Length-100, 100)); - if (bytes.Length >= 6200) await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(6000, 100)); } @@ -161,11 +185,15 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase MemoryStream ms = new(rangeBytes); ReadOnlyMemory? chunk; + int readBytes = 0; while ((chunk = await rangeReader!.ReadChunk()) != null) { + readBytes += chunk.Value.Length; ms.Write(chunk.Value.Span); } + Assert.Equal(range.Length, (ulong)readBytes); + Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice((int)range.Offset, (int)range.Length)), SHA256.HashData(rangeBytes)); _testOutputHelper.WriteLine($"\t\trange {range.Offset};{range.Length} validated"); @@ -173,7 +201,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase private static async Task ValidatePatch(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) { - if (bytes.Length < 1024 + 64) + if (bytes.Length < 1024 + 64 || bytes.Length > 5900) return; var patch = new byte[1024]; @@ -214,7 +242,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase for (int i = (int)rangeEnd; i < bytes.Length; i++) Assert.Equal(downloadedBytes[i], bytes[i]); } - + private async Task ValidateFilters(IFrostFSClient client, FrostFsContainerId containerId, FrostFsObjectId objectId, SplitId? splitId, ulong length) { @@ -237,13 +265,8 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, length)); - // var checkSum = CheckSum.CreateCheckSum(hash); - // await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); - await CheckFilter(client, containerId, new FilterByPhysicallyStored()); } @@ -265,7 +288,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase } } - private static async Task ValidateContent(IFrostFSClient client, FrostFsContainerId containerId, byte[] hash, FrostFsObjectId objectId) + private static async Task ValidateContent(IFrostFSClient client, FrostFsContainerId containerId, byte[] hash, FrostFsObjectId objectId) { var @object = await client.GetObjectAsync( new PrmObjectGet(containerId, objectId), @@ -283,6 +306,39 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase Assert.Equal(hash, SHA256.HashData(downloadedBytes)); } + private static async Task ValidateHeader( + IFrostFSClient client, + FrostFsContainerId containerId, + FrostFsObjectId objectId, + FrostFsHeaderResult expected) + { + var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, default), default); + + var objHeader = res.HeaderInfo; + Assert.NotNull(objHeader); + + Assert.Equal(containerId.GetValue(), objHeader.ContainerId.GetValue()); + + Assert.Equal(expected.HeaderInfo!.OwnerId!.Value, objHeader.OwnerId!.Value); + Assert.Equal(expected.HeaderInfo.Version!.Major, objHeader.Version!.Major); + Assert.Equal(expected.HeaderInfo.Version!.Minor, objHeader.Version!.Minor); + + Assert.Equal(expected.HeaderInfo.PayloadLength, objHeader.PayloadLength); + + Assert.Equal(expected.HeaderInfo.ObjectType, objHeader.ObjectType); + + if (expected.HeaderInfo.Attributes != null) + { + Assert.NotNull(objHeader.Attributes); + Assert.Equal(expected.HeaderInfo.Attributes.Count, objHeader.Attributes.Count); + + Assert.True(expected.HeaderInfo.Attributes.SequenceEqual(objHeader.Attributes)); + } + + Assert.Null(objHeader.Split); + } + + private static async Task CreateObjectServerCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) { var header = new FrostFsObjectHeader( @@ -310,7 +366,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase return await client.PutClientCutObjectAsync(param, default).ConfigureAwait(true); } - private static async Task PutSingleObject(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) + private static async Task PutSingleObject(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) { var header = new FrostFsObjectHeader( containerId: containerId, @@ -324,7 +380,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase return await client.PutSingleObjectAsync(param, default).ConfigureAwait(true); } - + private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) { var resultObjectsCount = 0; From 9364d60b96d9c71120afec6242dbbed27afb7bb8 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Tue, 4 Mar 2025 19:45:40 +0300 Subject: [PATCH 45/65] [#36] Client: Remove .net Range implementation Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Cryptography/Base58.cs | 10 +- src/FrostFS.SDK.Cryptography/Key.cs | 14 +- src/FrostFS.SDK.Cryptography/Range.cs | 255 ------------------------- 3 files changed, 12 insertions(+), 267 deletions(-) delete mode 100644 src/FrostFS.SDK.Cryptography/Range.cs diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 1b8f084..636eb0a 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -19,14 +19,14 @@ public static class Base58 if (buffer.Length < 4) throw new FormatException(); - byte[] checksum = buffer[0..(buffer.Length - 4)].Sha256().Sha256(); + var check = buffer.AsSpan(0, buffer.Length - 4).ToArray(); + byte[] checksum = check.Sha256().Sha256(); - if (!buffer.AsSpan(buffer.Length - 4).SequenceEqual(checksum[..4].AsSpan())) + if (!buffer.AsSpan(buffer.Length - 4).SequenceEqual(checksum.AsSpan(0, 4))) throw new FormatException(); - var ret = buffer[..^4]; Array.Clear(buffer, 0, buffer.Length); - return ret; + return check; } public static string Base58CheckEncode(this ReadOnlySpan data) @@ -35,7 +35,7 @@ public static class Base58 Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); - checksum[..4].AsSpan().CopyTo(buffer[data.Length..]); + checksum.AsSpan(0, 4).CopyTo(buffer.Slice(data.Length)); var ret = Encode(buffer); buffer.Clear(); diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index 0c9fd3b..cf9c3bd 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -81,7 +81,7 @@ public static class KeyExtension { Span data = stackalloc byte[21]; data[0] = version; - scriptHash.CopyTo(data[1..]); + scriptHash.CopyTo(data.Slice(1)); return Base58.Base58CheckEncode(data); } @@ -152,7 +152,7 @@ public static class KeyExtension { var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)) - .GetEncoded(false)[1..]; + .GetEncoded(false).Skip(1); var key = ECDsa.Create(new ECParameters { @@ -160,8 +160,8 @@ public static class KeyExtension D = privateKey, Q = new ECPoint { - X = publicKey[..32], - Y = publicKey[32..] + X = publicKey.Take(32).ToArray(), + Y = publicKey.Skip(32).ToArray() } }); @@ -177,14 +177,14 @@ public static class KeyExtension public static ECDsa LoadPublicKey(this byte[] publicKey) { - var publicKeyFull = publicKey.Decompress()[1..]; + var publicKeyFull = publicKey.Decompress().Skip(1); var key = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { - X = publicKeyFull[..32], - Y = publicKeyFull[32..] + X = publicKeyFull.Take(32).ToArray(), + Y = publicKeyFull.Skip(32).ToArray() } }); diff --git a/src/FrostFS.SDK.Cryptography/Range.cs b/src/FrostFS.SDK.Cryptography/Range.cs deleted file mode 100644 index 495479d..0000000 --- a/src/FrostFS.SDK.Cryptography/Range.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace System -{ - /// Represent a type can be used to index a collection either from the start or the end. - /// - /// Index is used by the C# compiler to support the new index syntax - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; - /// int lastElement = someArray[^1]; // lastElement = 5 - /// - /// - internal readonly struct Index : IEquatable - { - private readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - if (fromEnd) - _value = ~value; - else - _value = value; - } - - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { - _value = value; - } - - /// Create an Index pointing at first element. - public static Index Start => new(0); - - /// Create an Index pointing at beyond last element. - public static Index End => new(~0); - - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - return new Index(value); - } - - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - } - - return new Index(~value); - } - - /// Returns the index value. - public int Value - { - get - { - return _value < 0 ? ~_value : _value; - } - } - - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) - { - var offset = _value; - if (IsFromEnd) - { - offset += length + 1; - } - - return offset; - } - - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => value is Index index && _value == index._value; - - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => _value == other._value; - - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; - - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); - - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() - { - if (IsFromEnd) - return $"^{(uint)Value}"; - - return $"{(uint)Value}"; - } - } - - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - internal readonly struct Range(Index start, Index end) : IEquatable - { - /// Represent the inclusive start index of the Range. - public Index Start { get; } = start; - - /// Represent the exclusive end index of the Range. - public Index End { get; } = end; - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() - { - return Start.GetHashCode() * 31 + End.GetHashCode(); - } - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { - return Start + ".." + End; - } - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start; - var startIndex = Start; - if (startIndex.IsFromEnd) - start = length - startIndex.Value; - else - start = startIndex.Value; - - int end; - var endIndex = End; - if (endIndex.IsFromEnd) - end = length - endIndex.Value; - else - end = endIndex.Value; - - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - throw new ArgumentOutOfRangeException(nameof(length)); - } - - return (start, end - start); - } - } -} - -namespace System.Runtime.CompilerServices -{ - internal static class RuntimeHelpers - { - /// - /// Slices the specified array using the specified range. - /// - public static T[] GetSubArray(T[] array, Range range) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - (int offset, int length) = range.GetOffsetAndLength(array.Length); - - if (default(T) != null || typeof(T[]) == array.GetType()) - { - // We know the type of the array to be exactly T[]. - - if (length == 0) - { - return []; - } - - var dest = new T[length]; - Array.Copy(array, offset, dest, 0, length); - return dest; - } - else - { - // The array is actually a U[] where U:T. - var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length); - Array.Copy(array, offset, dest, 0, length); - return dest; - } - } - } -} From d6fe0344538a223303c9295452f0ad73681ca376 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Tue, 4 Mar 2025 20:12:42 +0300 Subject: [PATCH 46/65] [#37] Client: Add AssemblyInfo files Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/AssemblyInfo.cs | 11 ++++++++++ .../FrostFS.SDK.Client.csproj | 16 ++++++++++---- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 21 ++++++------------- .../FrostFS.SDK.Cryptography.csproj | 8 +++++++ src/FrostFS.SDK.Protos/AssemblyInfo.cs | 11 ++++++++++ .../FrostFS.SDK.Protos.csproj | 8 +++++++ 6 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 src/FrostFS.SDK.Client/AssemblyInfo.cs create mode 100644 src/FrostFS.SDK.Protos/AssemblyInfo.cs diff --git a/src/FrostFS.SDK.Client/AssemblyInfo.cs b/src/FrostFS.SDK.Client/AssemblyInfo.cs new file mode 100644 index 0000000..9f8605f --- /dev/null +++ b/src/FrostFS.SDK.Client/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("FrostFS.SDK.Client")] +[assembly: AssemblyProduct("FrostFS.SDK.Client")] +[assembly: AssemblyTitle("FrostFS.SDK.Client")] + +[assembly: AssemblyVersion("1.0.1")] +[assembly: AssemblyFileVersion("1.0.1")] + +[assembly: ComVisible(false)] diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 2518ce6..331e827 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -8,19 +8,27 @@ - true + true - true + true - <_SkipUpgradeNetAnalyzersNuGetWarning>true + <_SkipUpgradeNetAnalyzersNuGetWarning>true - true + true + + + + true + + + + Assemblyinfo.cs diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index a75808b..7e34e5b 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,20 +1,11 @@ -using System.Runtime.CompilerServices; +using System.Reflection; using System.Runtime.InteropServices; -// In SDK-style projects such as this one, several assembly attributes that were historically -// defined in this file are now automatically added during build and populated with -// values defined in project properties. For details of which attributes are included -// and how to customise this process see: https://aka.ms/assembly-info-properties +[assembly: AssemblyCompany("FrostFS.SDK.Cryptography")] +[assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] +[assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] - -// Setting ComVisible to false makes the types in this assembly not visible to COM -// components. If you need to access a type in this assembly from COM, set the ComVisible -// attribute to true on that type. +[assembly: AssemblyVersion("1.0.1")] +[assembly: AssemblyFileVersion("1.0.1")] [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM. - -[assembly: Guid("08a8487e-39ce-41fb-9c24-13f73ff2bde0")] - -[assembly: InternalsVisibleTo("FrostFS.SDK.Cryptography.Test")] \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 53c9003..484bc1a 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -18,6 +18,14 @@ true + + true + + + + Assemblyinfo.cs + + diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs new file mode 100644 index 0000000..bc2beee --- /dev/null +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyCompany("FrostFS.SDK.Protos")] +[assembly: AssemblyProduct("FrostFS.SDK.Protos")] +[assembly: AssemblyTitle("FrostFS.SDK.Protos")] + +[assembly: AssemblyVersion("1.0.1")] +[assembly: AssemblyFileVersion("1.0.1")] + +[assembly: ComVisible(false)] diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 8e7250a..cd6627e 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -18,6 +18,14 @@ true + + true + + + + Assemblyinfo.cs + + From 32a7e645381e67a8fa8892dc42bcce87810fb5db Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 7 Mar 2025 15:29:45 +0300 Subject: [PATCH 47/65] [#39] Client: add memory usage optimizations Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/ApeRules/Resources.cs | 2 +- src/FrostFS.SDK.Client/AssemblyInfo.cs | 9 +- .../FrostFS.SDK.Client.csproj | 1 + .../Models/Netmap/FrostFsPlacementPolicy.cs | 2 +- .../Models/Netmap/Placement/Context.cs | 2 +- .../Models/Object/FrostFsObject.cs | 38 +++++--- .../Services/ObjectServiceProvider.cs | 6 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 19 +++- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 9 +- src/FrostFS.SDK.Cryptography/Extentions.cs | 23 ++++- .../FrostFS.SDK.Cryptography.csproj | 1 + src/FrostFS.SDK.Cryptography/Key.cs | 4 +- src/FrostFS.SDK.Protos/AssemblyInfo.cs | 9 +- src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 87 ++++++++----------- 14 files changed, 120 insertions(+), 92 deletions(-) diff --git a/src/FrostFS.SDK.Client/ApeRules/Resources.cs b/src/FrostFS.SDK.Client/ApeRules/Resources.cs index 4f431a6..ef06b4b 100644 --- a/src/FrostFS.SDK.Client/ApeRules/Resources.cs +++ b/src/FrostFS.SDK.Client/ApeRules/Resources.cs @@ -6,7 +6,7 @@ public struct Resources(bool inverted, string[] names) : System.IEquatable + all diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs index abfce6c..61bdb82 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsPlacementPolicy.cs @@ -14,7 +14,7 @@ public struct FrostFsPlacementPolicy(bool unique, params FrostFsReplica[] replicas) : IEquatable { - private PlacementPolicy policy; + private PlacementPolicy? policy; public FrostFsReplica[] Replicas { get; } = replicas; diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs index ed4ad82..cc5c20a 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs @@ -229,7 +229,7 @@ internal struct Context return [.. result]; } - static double CalcBucketWeight(List ns, IAggregator a, Func wf) + static double CalcBucketWeight(List ns, MeanIQRAgg a, Func wf) { foreach (var node in ns) { diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs index dfd2a22..c39a587 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs @@ -4,7 +4,9 @@ namespace FrostFS.SDK; public class FrostFsObject { - private byte[]? bytes; + // private byte[]? _payloadBytes; + // private ReadOnlyMemory _payloadMemory; + // private bool _isInitPayloadMemory; /// /// Creates new instance from ObjectHeader @@ -45,21 +47,31 @@ public class FrostFsObject /// Reader for received data public IObjectReader? ObjectReader { get; set; } - public byte[] SingleObjectPayload - { - get { return bytes ?? []; } - set { bytes = value; } - } + public ReadOnlyMemory SingleObjectPayload { get; set; } + + //public ReadOnlyMemory SingleObjectPayloadMemory + //{ + // get + // { + // if (!_isInitPayloadMemory) + // { + // _payloadMemory = _payloadBytes.AsMemory(); + // _isInitPayloadMemory = true; + // } + + // return _payloadMemory; + // } + // set + // { + // _payloadMemory = value; + // _isInitPayloadMemory = true; + // } + //} /// - /// The size of payload cannot exceed MaxObjectSize value from NetworkSettings - /// Used only for PutSingleObject method + /// Provide SHA256 hash of the payload. If null, the hash is calculated by internal logic /// - /// Buffer for output data - public void SetSingleObjectPayload(byte[] bytes) - { - this.bytes = bytes; - } + public byte[]? PayloadHash { get; set; } /// /// Applied only for the last Object in chain in case of manual multipart uploading diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index b5b5bde..8992733 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -327,10 +327,10 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Body = new() { - Address = address, + Address = address, Patch = new PatchRequest.Types.Body.Types.Patch { - Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount), + Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory(0, bytesCount)), SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount } } } @@ -481,7 +481,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var obj = new FrostFsObject(partHeader) { - SingleObjectPayload = buffer.Length == size ? buffer : buffer[..size] + SingleObjectPayload = buffer.AsMemory(0, size) }; var prm = new PrmSingleObjectPut(obj); diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index ffc33a3..ac5d019 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -55,7 +55,15 @@ public static class ObjectTools var grpcHeader = @object.Header.GetHeader(); grpcHeader.PayloadLength = (ulong)@object.SingleObjectPayload.Length; - grpcHeader.PayloadHash = Sha256Checksum(@object.SingleObjectPayload); + + if (@object.PayloadHash != null) + { + grpcHeader.PayloadHash = ChecksumFromSha256(@object.PayloadHash); + } + else + { + grpcHeader.PayloadHash = Sha256Checksum(@object.SingleObjectPayload); + } var split = @object.Header.Split; @@ -148,6 +156,15 @@ public static class ObjectTools }; } + internal static Checksum Sha256Checksum(ReadOnlyMemory data) + { + return new Checksum + { + Type = ChecksumType.Sha256, + Sum = ByteString.CopyFrom(data.Sha256()) + }; + } + internal static Checksum ChecksumFromSha256(ReadOnlyMemory dataHash) { return new Checksum diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index 7e34e5b..addd4ad 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,11 +1,8 @@ using System.Reflection; -using System.Runtime.InteropServices; [assembly: AssemblyCompany("FrostFS.SDK.Cryptography")] +[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] - -[assembly: AssemblyVersion("1.0.1")] -[assembly: AssemblyFileVersion("1.0.1")] - -[assembly: ComVisible(false)] +[assembly: AssemblyVersion("1.0.2.0")] diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index a3f051f..fc51c10 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,6 +1,7 @@ +using System; using System.Security.Cryptography; using System.Threading; - +using CommunityToolkit.HighPerformance; using Org.BouncyCastle.Crypto.Digests; namespace FrostFS.SDK.Cryptography; @@ -35,7 +36,27 @@ public static class Extentions finally { if (lockTaken) + { _spinlockSha256.Exit(false); + } + } + } + + public static byte[] Sha256(this ReadOnlyMemory value) + { + bool lockTaken = false; + try + { + _spinlockSha256.Enter(ref lockTaken); + + return _sha256.ComputeHash(value.AsStream()); + } + finally + { + if (lockTaken) + { + _spinlockSha256.Exit(false); + } } } diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 484bc1a..c9a51d3 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -28,6 +28,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index cf9c3bd..072f155 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -152,7 +152,7 @@ public static class KeyExtension { var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)) - .GetEncoded(false).Skip(1); + .GetEncoded(false).Skip(1).ToArray(); var key = ECDsa.Create(new ECParameters { @@ -177,7 +177,7 @@ public static class KeyExtension public static ECDsa LoadPublicKey(this byte[] publicKey) { - var publicKeyFull = publicKey.Decompress().Skip(1); + var publicKeyFull = publicKey.Decompress().Skip(1).ToArray(); var key = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index bc2beee..eb6f873 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -1,11 +1,8 @@ using System.Reflection; -using System.Runtime.InteropServices; [assembly: AssemblyCompany("FrostFS.SDK.Protos")] +[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] - -[assembly: AssemblyVersion("1.0.1")] -[assembly: AssemblyFileVersion("1.0.1")] - -[assembly: ComVisible(false)] +[assembly: AssemblyVersion("1.0.2.0")] diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 564627d..479c6ba 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -77,68 +77,53 @@ public class ObjectTest : ObjectTestsBase var singleObjects = Mocker.PutSingleRequests.ToArray(); Assert.NotNull(Mocker.ClientStreamWriter?.Messages); - var streamObjects = Mocker.ClientStreamWriter.Messages.ToArray(); - Assert.Single(singleObjects); + var objects = Mocker.PutSingleRequests.Select(o => o.Body.Object).ToArray(); - Assert.Equal(11, streamObjects.Length); + Assert.Equal(4, objects.Length); - var bodies = streamObjects.Select(o => ((Object.PutRequest)o).Body).ToArray(); + // linked object + Assert.Equal(0, objects[3].Payload.Length); - Assert.Equal(3, bodies.Count(b => b.Init != null)); - Assert.Equal(5, bodies.Count(b => b.Chunk.Length == 1024)); + // PART1 + Assert.Equal(blockSize, objects[0].Payload.Length); + Assert.True(bytes.AsMemory(0, blockSize).ToArray().SequenceEqual(objects[0].Payload)); + + Assert.NotNull(objects[0].Header.Split.SplitId); + Assert.Null(objects[0].Header.Split.Previous); + Assert.True(objects[0].Header.Attributes.Count == 0); + Assert.Null(objects[0].Header.Split.Parent); - Assert.Equal(596, ((Object.PutRequest)streamObjects[10]).Body.Chunk.Length); + // PART2 + Assert.Equal(blockSize, objects[1].Payload.Length); + Assert.True(bytes.AsMemory(blockSize, blockSize).ToArray().SequenceEqual(objects[1].Payload)); - var linkObject = singleObjects[0].Body.Object; - var header1 = bodies[0].Init.Header; - var header2 = bodies[4].Init.Header; - var header3 = bodies[8].Init.Header; - - var payload1 = bodies[1].Chunk - .Concat(bodies[2].Chunk) - .Concat(bodies[3].Chunk) - .ToArray(); - - var payload2 = bodies[5].Chunk - .Concat(bodies[6].Chunk) - .Concat(bodies[7].Chunk) - .ToArray(); - - var payload3 = bodies[9].Chunk - .Concat(bodies[10].Chunk) - .ToArray(); - - Assert.NotNull(header1.Split.SplitId); - Assert.Null(header1.Split.Previous); - Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(0, blockSize).ToArray()), SHA256.HashData(payload1)); - Assert.True(header1.Attributes.Count == 0); - - Assert.Equal(header1.Split.SplitId, header2.Split.SplitId); - Assert.Equal(objIds.ElementAt(0), header2.Split.Previous.Value); - Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(blockSize, blockSize).ToArray()), SHA256.HashData(payload2)); - Assert.True(header2.Attributes.Count == 0); + Assert.Equal(objects[0].Header.Split.SplitId, objects[1].Header.Split.SplitId); + Assert.True(objects[1].Header.Attributes.Count == 0); + Assert.Null(objects[1].Header.Split.Parent); // last part - Assert.NotNull(header3.Split.Parent); - Assert.NotNull(header3.Split.ParentHeader); - Assert.NotNull(header3.Split.ParentSignature); - Assert.Equal(header2.Split.SplitId, header3.Split.SplitId); - Assert.Equal(SHA256.HashData(bytes.AsMemory().Slice(fileLength - fileLength % blockSize, fileLength % blockSize).ToArray()), SHA256.HashData(payload3)); - Assert.True(header3.Attributes.Count == 0); + Assert.Equal(bytes.Length % blockSize, objects[2].Payload.Length); + Assert.True(bytes.AsMemory(2*blockSize).ToArray().SequenceEqual(objects[2].Payload)); - //link object - Assert.Equal(header3.Split.Parent, linkObject.Header.Split.Parent); - Assert.Equal(header3.Split.ParentHeader, linkObject.Header.Split.ParentHeader); - Assert.Equal(header3.Split.SplitId, linkObject.Header.Split.SplitId); - Assert.Equal(0, (int)linkObject.Header.PayloadLength); - Assert.True(header3.Attributes.Count == 0); + Assert.NotNull(objects[3].Header.Split.Parent); + Assert.NotNull(objects[3].Header.Split.ParentHeader); + Assert.NotNull(objects[3].Header.Split.ParentSignature); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.True(objects[2].Header.Attributes.Count == 0); - Assert.Single(linkObject.Header.Split.ParentHeader.Attributes); - Assert.Equal("k", linkObject.Header.Split.ParentHeader.Attributes[0].Key); - Assert.Equal("v", linkObject.Header.Split.ParentHeader.Attributes[0].Value); + // link object + Assert.Equal(objects[2].Header.Split.Parent, objects[3].Header.Split.Parent); + Assert.Equal(objects[2].Header.Split.ParentHeader, objects[3].Header.Split.ParentHeader); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.Equal(0, (int)objects[3].Header.PayloadLength); + Assert.True(objects[3].Header.Attributes.Count == 0); - var modelObjId = FrostFsObjectId.FromHash(linkObject.Header.Split.Parent.Value.ToByteArray()); + Assert.Single(objects[3].Header.Split.ParentHeader.Attributes); + Assert.Equal("k", objects[3].Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", objects[3].Header.Split.ParentHeader.Attributes[0].Value); + + var modelObjId = FrostFsObjectId.FromHash(objects[3].Header.Split.Parent.Value.ToByteArray()); Assert.Equal(result.Value, modelObjId.ToString()); } From 809bd9035204bb33c1f6615cb57314f764b35222 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Tue, 11 Mar 2025 22:56:28 +0300 Subject: [PATCH 48/65] [#40] Client: Add memory optimization for hash Signed-off-by: Pavel Gross --- .../FrostFS.SDK.Client.csproj | 2 +- .../Models/Session/FrostFsSessionToken.cs | 4 +- src/FrostFS.SDK.Client/ObjectWriter.cs | 2 +- .../Services/AccountingServiceProvider.cs | 2 +- .../Services/ApeManagerServiceProvider.cs | 6 +- .../Services/ContainerServiceProvider.cs | 12 ++-- .../Services/NetmapServiceProvider.cs | 6 +- .../Services/ObjectServiceProvider.cs | 27 +++++---- .../Services/SessionServiceProvider.cs | 2 +- src/FrostFS.SDK.Client/Tools/RequestSigner.cs | 57 ++++++++++++++---- src/FrostFS.SDK.Client/Tools/Verifier.cs | 5 +- src/FrostFS.SDK.Cryptography/Extentions.cs | 21 ++++++- src/FrostFS.SDK.Cryptography/HashStream.cs | 58 +++++++++++++++++++ .../Mocks/AsyncStreamReaderMock.cs | 4 +- .../ContainerServiceBase.cs | 14 ++--- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 4 +- .../Smoke/Client/ObjectTests/ObjectTests.cs | 8 +-- 17 files changed, 170 insertions(+), 64 deletions(-) create mode 100644 src/FrostFS.SDK.Cryptography/HashStream.cs diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index e75da94..7e1a0bf 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -4,7 +4,7 @@ netstandard2.0 12.0 enable - AllEnabledByDefault + AllEnabledByDefault diff --git a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs index 708c9f6..808e91b 100644 --- a/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs +++ b/src/FrostFS.SDK.Client/Models/Session/FrostFsSessionToken.cs @@ -81,7 +81,7 @@ public class FrostFsSessionToken } sessionToken.Body.SessionKey = key.PublicKeyProto; - sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); return sessionToken; } @@ -116,7 +116,7 @@ public class FrostFsSessionToken Verb = verb }; - sessionToken.Signature = key.ECDsaKey.SignMessagePart(sessionToken.Body); + sessionToken.Signature = key.SignMessagePart(sessionToken.Body); return sessionToken; } diff --git a/src/FrostFS.SDK.Client/ObjectWriter.cs b/src/FrostFS.SDK.Client/ObjectWriter.cs index 5683740..a1859cc 100644 --- a/src/FrostFS.SDK.Client/ObjectWriter.cs +++ b/src/FrostFS.SDK.Client/ObjectWriter.cs @@ -34,7 +34,7 @@ namespace FrostFS.SDK.Client chunkRequest.AddMetaHeader(args.XHeaders); - chunkRequest.Sign(this.ctx.Key.ECDsaKey); + chunkRequest.Sign(this.ctx.Key); await streamer.Write(chunkRequest).ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs index e87674f..ea45b37 100644 --- a/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/AccountingServiceProvider.cs @@ -27,7 +27,7 @@ internal sealed class AccountingServiceProvider : ContextAccessor }; request.AddMetaHeader([]); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await _accountingServiceClient!.BalanceAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 5331b80..8ad35d5 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -32,7 +32,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await _apeManagerServiceClient!.AddChainAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -53,7 +53,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await _apeManagerServiceClient!.RemoveChainAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -71,7 +71,7 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await _apeManagerServiceClient!.ListChainsAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index d7ba34c..59783cf 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -39,7 +39,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService internal async Task GetContainerAsync(PrmContainerGet args, CallContext ctx) { - GetRequest request = GetContainerRequest(args.Container.GetContainerID(), args.XHeaders, ClientContext.Key.ECDsaKey); + GetRequest request = GetContainerRequest(args.Container.GetContainerID(), args.XHeaders, ClientContext.Key); var response = await service.GetAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -59,7 +59,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService }; request.AddMetaHeader(args.XHeaders); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await service.ListAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -96,7 +96,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await service.PutAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -127,7 +127,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await service.DeleteAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -139,7 +139,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Verifier.CheckResponse(response); } - private static GetRequest GetContainerRequest(ContainerID id, string[] xHeaders, ECDsa key) + private static GetRequest GetContainerRequest(ContainerID id, string[] xHeaders, ClientKey key) { var request = new GetRequest { @@ -163,7 +163,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService private async Task WaitForContainer(WaitExpects expect, ContainerID id, PrmWait waitParams, CallContext ctx) { - var request = GetContainerRequest(id, [], ClientContext.Key.ECDsaKey); + var request = GetContainerRequest(id, [], ClientContext.Key); async Task action() { diff --git a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs index a1bf17b..672c010 100644 --- a/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/NetmapServiceProvider.cs @@ -50,7 +50,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor }; request.AddMetaHeader([]); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await netmapServiceClient.LocalNodeInfoAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -64,7 +64,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor var request = new NetworkInfoRequest(); request.AddMetaHeader([]); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await netmapServiceClient.NetworkInfoAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken) .ConfigureAwait(false); @@ -79,7 +79,7 @@ internal sealed class NetmapServiceProvider : ContextAccessor var request = new NetmapSnapshotRequest(); request.AddMetaHeader([]); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await netmapServiceClient.NetmapSnapshotAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 8992733..f220d00 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -67,7 +67,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await client!.HeadAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken).ConfigureAwait(false); @@ -111,7 +111,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); return await GetObject(request, ctx).ConfigureAwait(false); } @@ -145,7 +145,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var call = client.GetRange(request, null, ctx.GetDeadline(), ctx.CancellationToken); return new RangeReader(call); @@ -185,7 +185,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await client.GetRangeHashAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -218,7 +218,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl ClientContext.Key); request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await client.DeleteAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken); @@ -247,7 +247,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); using var stream = GetSearchReader(request, ctx); @@ -283,7 +283,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders, protoToken); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); var response = await client.PutSingleAsync(request, null, ctx.GetDeadline(), ctx.CancellationToken).ConfigureAwait(false); @@ -363,7 +363,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl request.AddMetaHeader(args.XHeaders); } - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); await call.RequestStream.WriteAsync(request).ConfigureAwait(false); @@ -402,11 +402,11 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl throw new ArgumentException("The stream has zero length"); var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); - args.PutObjectContext.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize; + var partSize = (int)networkSettings.MaxObjectSize; var restBytes = args.PutObjectContext.FullLength; - var objectSize = (int)Math.Min((ulong)args.PutObjectContext.MaxObjectSizeCache, restBytes); + var objectSize = (int)Math.Min((ulong)partSize, restBytes); // define collection capacity var objectsCount = (int)(restBytes / (ulong)objectSize) + ((restBytes % (ulong)objectSize) > 0 ? 1 : 0); @@ -414,6 +414,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl // if the object fits one part, it can be loaded as non-complex object if (objectsCount == 1) { + args.PutObjectContext.MaxObjectSizeCache = partSize; var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); return singlePartResult.ObjectId; } @@ -422,8 +423,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl SplitId splitId = new(); - var partSize = args.PutObjectContext.MaxObjectSizeCache; - // keep attributes for the large object var attributes = args.Header!.Attributes.ToArray(); header.Attributes = null; @@ -578,7 +577,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl }; chunkRequest.AddMetaHeader(args.XHeaders); - chunkRequest.Sign(ClientContext.Key.ECDsaKey); + chunkRequest.Sign(ClientContext.Key); await stream.Write(chunkRequest).ConfigureAwait(false); } @@ -640,7 +639,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl initRequest.AddMetaHeader(args.XHeaders, protoToken); - initRequest.Sign(ClientContext.Key.ECDsaKey); + initRequest.Sign(ClientContext.Key); return await PutObjectInit(initRequest, ctx).ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs index e73a3d3..cde2b9e 100644 --- a/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/SessionServiceProvider.cs @@ -26,7 +26,7 @@ internal sealed class SessionServiceProvider : ContextAccessor }; request.AddMetaHeader(args.XHeaders); - request.Sign(ClientContext.Key.ECDsaKey); + request.Sign(ClientContext.Key); return await CreateSession(request, ctx).ConfigureAwait(false); } diff --git a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs index f747b3f..3070edf 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Security.Cryptography; using FrostFS.Refs; @@ -13,7 +14,7 @@ using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; - +using Org.BouncyCastle.Utilities; using Signature = FrostFS.Refs.Signature; namespace FrostFS.SDK.Client; @@ -74,7 +75,7 @@ public static class RequestSigner }; } - public static ByteString SignData(this ECDsa key, byte[] data) + public static ByteString SignData(this ECDsa key, ReadOnlyMemory data) { if (key is null) { @@ -84,27 +85,61 @@ public static class RequestSigner Span result = stackalloc byte[65]; result[0] = 0x04; - //var hash = new byte[65]; - //hash[0] = 0x04; - key.SignHash(data.Sha512()).AsSpan().CopyTo(result[1..]); return ByteString.CopyFrom(result); } - - internal static Signature SignMessagePart(this ECDsa key, IMessage? data) + + public static ByteString SignDataByHash(this ECDsa key, byte[] hash) { - var data2Sign = data is null ? [] : data.ToByteArray(); + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + + Span result = stackalloc byte[65]; + result[0] = 0x04; + + key.SignHash(hash).AsSpan().CopyTo(result[1..]); + + return ByteString.CopyFrom(result); + } + + internal static Signature SignMessagePart(this ClientKey key, IMessage? data) + { + if (data is null) + { + return new Signature + { + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignData(ReadOnlyMemory.Empty), + }; + } + + var size = data.CalculateSize(); + + if (size == 0) + { + return new Signature + { + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignData(ReadOnlyMemory.Empty), + }; + } + + using HashStream stream = new(); + data.WriteTo(stream); + var sig = new Signature { - Key = ByteString.CopyFrom(key.PublicKey()), - Sign = key.SignData(data2Sign), + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignDataByHash(stream.Hash()) }; return sig; } - internal static void Sign(this IVerifiableMessage message, ECDsa key) + internal static void Sign(this IVerifiableMessage message, ClientKey key) { var meta = message.GetMetaHeader(); IVerificationHeader verify = message switch diff --git a/src/FrostFS.SDK.Client/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs index 063fa21..ec70d20 100644 --- a/src/FrostFS.SDK.Client/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -63,14 +63,11 @@ public static class Verifier return signature.Key.ToByteArray().VerifyRFC6979(message.ToByteArray(), signature.Sign.ToByteArray()); } - public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig) + public static bool VerifyData(this ECDsa key, ReadOnlyMemory data, byte[] sig) { if (key is null) throw new ArgumentNullException(nameof(key)); - if (data is null) - throw new ArgumentNullException(nameof(data)); - if (sig is null) throw new ArgumentNullException(nameof(sig)); diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index fc51c10..40ad6f0 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Security.Cryptography; using System.Threading; using CommunityToolkit.HighPerformance; @@ -60,14 +61,30 @@ public static class Extentions } } - public static byte[] Sha512(this byte[] value) + public static byte[] Sha512(this ReadOnlyMemory value) { bool lockTaken = false; try { _spinlockSha512.Enter(ref lockTaken); - return _sha512.ComputeHash(value); + return _sha512.ComputeHash(value.AsStream()); + } + finally + { + if (lockTaken) + _spinlockSha512.Exit(false); + } + } + + public static byte[] Sha512(this Stream stream) + { + bool lockTaken = false; + try + { + _spinlockSha512.Enter(ref lockTaken); + + return _sha512.ComputeHash(stream); } finally { diff --git a/src/FrostFS.SDK.Cryptography/HashStream.cs b/src/FrostFS.SDK.Cryptography/HashStream.cs new file mode 100644 index 0000000..d2ede1a --- /dev/null +++ b/src/FrostFS.SDK.Cryptography/HashStream.cs @@ -0,0 +1,58 @@ +using System.IO; +using System.Security.Cryptography; + +namespace FrostFS.SDK.Cryptography; + +public sealed class HashStream() : Stream +{ + private long position; + + private readonly SHA512 _hash = SHA512.Create(); + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => long.MaxValue; + + public override long Position + { + get { return position; } + set { position = value; } + } + + public override void Flush() + { } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + public override void SetLength(long value) + { } + + public override void Write(byte[] buffer, int offset, int count) + { + _hash.TransformBlock(buffer, offset, count, buffer, offset); + } + + public byte[] Hash() + { + _hash.TransformFinalBlock([], 0, 0); + return _hash.Hash; + } + + protected override void Dispose(bool disposing) + { + _hash?.Dispose(); + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 9e9a18d..0a3aec6 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -42,8 +42,8 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) ObjectId = new Refs.ObjectID { Value = ByteString.CopyFrom(SHA256.HashData(Array.Empty())) }, Signature = new Refs.Signature { - Key = ByteString.CopyFrom(Key.PublicKey()), - Sign = Key.SignData(header.ToByteArray()), + Key = Key.PublicKeyProto, + Sign = Key.ECDsaKey. SignData(header.ToByteArray()), } } }, diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index ab52c19..e6f25c2 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -18,7 +18,7 @@ namespace FrostFS.SDK.Tests; public abstract class ServiceBase(string key) { public string StringKey { get; private set; } = key; - public ECDsa Key { get; private set; } = key.LoadWif(); + public ClientKey Key { get; private set; } = new ClientKey(key.LoadWif()); public FrostFsVersion Version { get; set; } = DefaultVersion; public FrostFsPlacementPolicy PlacementPolicy { get; set; } = DefaultPlacementPolicy; @@ -44,21 +44,21 @@ public abstract class ServiceBase(string key) { MetaSignature = new Refs.Signature { - Key = ByteString.CopyFrom(Key.PublicKey()), + Key = Key.PublicKeyProto, Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = Key.SignData(response.MetaHeader.ToByteArray()) + Sign = Key.ECDsaKey.SignData(response.MetaHeader.ToByteArray()) }, BodySignature = new Refs.Signature { - Key = ByteString.CopyFrom(Key.PublicKey()), + Key = Key.PublicKeyProto, Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = Key.SignData(response.GetBody().ToByteArray()) + Sign = Key.ECDsaKey.SignData(response.GetBody().ToByteArray()) }, OriginSignature = new Refs.Signature { - Key = ByteString.CopyFrom(Key.PublicKey()), + Key = Key.PublicKeyProto, Scheme = Refs.SignatureScheme.EcdsaRfc6979Sha256, - Sign = Key.SignData([]) + Sign = Key.ECDsaKey.SignData(ReadOnlyMemory.Empty) } }; diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 4f07e41..49a0f6d 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -92,8 +92,8 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) headResponse.Body.Header.Signature = new Refs.Signature { - Key = ByteString.CopyFrom(Key.PublicKey()), - Sign = Key.SignData(headResponse.Body.Header.ToByteArray()), + Key = Key.PublicKeyProto, + Sign = Key.ECDsaKey.SignData(headResponse.Body.Header.ToByteArray()), }; headResponse.VerifyHeader = GetResponseVerificationHeader(headResponse); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs index 81f0cd3..03a9169 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -55,10 +55,10 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase private async Task RunSuite(IFrostFSClient client, FrostFsContainerId containerId) { - int[] objectSizes = [1, 257, 6 * 1024, 20 * 1024]; + int[] objectSizes = [1, 257, 5 * 1024 * 1024, 20 * 1024 * 1024]; string[] objectTypes = [clientCut, serverCut, singleObject]; - + foreach (var objectSize in objectSizes) { _testOutputHelper.WriteLine($"test set for object size {objectSize}"); @@ -77,13 +77,13 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase break; case clientCut: objectId = await CreateObjectClientCut(client, containerId, bytes); - _testOutputHelper.WriteLine($"\tclient side cut"); + _testOutputHelper.WriteLine($"\tclient side cut"); break; case singleObject: if (objectSize > 1 * 1024 * 1024) continue; objectId = await PutSingleObject(client, containerId, bytes); - _testOutputHelper.WriteLine($"\tput single object"); + _testOutputHelper.WriteLine($"\tput single object"); break; default: From 6ae96c1d771924353f8658903bc35cf0f6c43fd0 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 12 Mar 2025 00:11:50 +0300 Subject: [PATCH 49/65] [#41] Client: Remove ranges Signed-off-by: Pavel Gross --- .../Models/Netmap/FrostFsNetmapSnapshot.cs | 2 +- .../Models/Netmap/Placement/Context.cs | 4 +- .../Services/ObjectServiceProvider.cs | 2 +- src/FrostFS.SDK.Client/Tools/Range.cs | 248 ------------------ src/FrostFS.SDK.Client/Tools/RequestSigner.cs | 9 +- src/FrostFS.SDK.Client/Tools/Verifier.cs | 6 +- 6 files changed, 11 insertions(+), 260 deletions(-) delete mode 100644 src/FrostFS.SDK.Client/Tools/Range.cs diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs index 1d90b04..e15c2a8 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsNetmapSnapshot.cs @@ -87,7 +87,7 @@ public class FrostFsNetmapSnapshot(ulong epoch, IReadOnlyList n if (expr.Selector == null) { - var lastFilter = expr.Filters[^1]; + var lastFilter = expr.Filters.Last(); var subCollestion = new List(); ret.Add(subCollestion); diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs index cc5c20a..cd287d0 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs @@ -271,7 +271,7 @@ internal struct Context var ns = buckets[i].nodes; if (ns.Length >= maxNodesInBucket) { - res.Add(new List(ns[..maxNodesInBucket])); + res.Add(ns.Take(maxNodesInBucket).ToList()); } else if (ns.Length >= nodesInBucket) { @@ -353,7 +353,7 @@ internal struct Context var start = hasPrefix ? likeWildcard.Length : 0; var end = hasSuffix ? f.Value.Length - likeWildcard.Length : f.Value.Length; - var str = f.Value[start..end]; + var str = f.Value.Substring(start, end-start); if (hasPrefix && hasSuffix) return nodeInfo.Attributes[f.Key].Contains(str); diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index f220d00..3ce543d 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -572,7 +572,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Body = new PutRequest.Types.Body { - Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory()[..bytesCount]) + Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory(0, bytesCount)) } }; diff --git a/src/FrostFS.SDK.Client/Tools/Range.cs b/src/FrostFS.SDK.Client/Tools/Range.cs deleted file mode 100644 index 8add54e..0000000 --- a/src/FrostFS.SDK.Client/Tools/Range.cs +++ /dev/null @@ -1,248 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace System -{ - /// Represent a type can be used to index a collection either from the start or the end. - /// - /// Index is used by the C# compiler to support the new index syntax - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; - /// int lastElement = someArray[^1]; // lastElement = 5 - /// - /// - internal readonly struct Index : IEquatable - { - private readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - if (fromEnd) - _value = ~value; - else - _value = value; - } - - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { - _value = value; - } - - /// Create an Index pointing at first element. - public static Index Start => new(0); - - /// Create an Index pointing at beyond last element. - public static Index End => new(~0); - - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - return new Index(value); - } - - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); - - return new Index(~value); - } - - /// Returns the index value. - public int Value - { - get - { - return _value < 0 ? ~_value : _value; - } - } - - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) - { - var offset = _value; - if (IsFromEnd) - { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - - offset += length + 1; - } - return offset; - } - - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => value is Index index && _value == index._value; - - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => _value == other._value; - - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; - - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); - - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() - { - if (IsFromEnd) - return $"^{(uint)Value}"; - - return $"{(uint)Value}"; - } - } - - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - internal readonly struct Range(Index start, Index end) : IEquatable - { - /// Represent the inclusive start index of the Range. - public Index Start { get; } = start; - - /// Represent the exclusive end index of the Range. - public Index End { get; } = end; - - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals(object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); - - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - - /// Returns the hash code for this instance. - public override int GetHashCode() - { - return Start.GetHashCode() * 31 + End.GetHashCode(); - } - - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() - { - return Start + ".." + End; - } - - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new(Index.Start, end); - - /// Create a Range object starting from first element to the end. - public static Range All => new(Index.Start, Index.End); - - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start; - var startIndex = Start; - - if (startIndex.IsFromEnd) - start = length - startIndex.Value; - else - start = startIndex.Value; - - int end; - var endIndex = End; - - if (endIndex.IsFromEnd) - end = length - endIndex.Value; - else - end = endIndex.Value; - - if ((uint)end > (uint)length || (uint)start > (uint)end) - throw new ArgumentOutOfRangeException(nameof(length)); - - return (start, end - start); - } - } -} - -namespace System.Runtime.CompilerServices -{ - internal static class RuntimeHelpers - { - /// - /// Slices the specified array using the specified range. - /// - public static T[] GetSubArray(T[] array, Range range) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - - (int offset, int length) = range.GetOffsetAndLength(array.Length); - - if (default(T) != null || typeof(T[]) == array.GetType()) - { - // We know the type of the array to be exactly T[]. - - if (length == 0) - return []; - - var dest = new T[length]; - Array.Copy(array, offset, dest, 0, length); - return dest; - } - else - { - // The array is actually a U[] where U:T. - var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length); - Array.Copy(array, offset, dest, 0, length); - return dest; - } - } - } -} diff --git a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs index 3070edf..80dfae7 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -49,14 +49,13 @@ public static class RequestSigner var sbytes = rs[1].ToByteArrayUnsigned(); var index = RFC6979SignatureSize / 2 - rbytes.Length; - rbytes.AsSpan().CopyTo(signature[index..]); + rbytes.AsSpan().CopyTo(signature.Slice(index)); index = RFC6979SignatureSize - sbytes.Length; - sbytes.AsSpan().CopyTo(signature[index..]); + sbytes.AsSpan().CopyTo(signature.Slice(index)); return ByteString.CopyFrom(signature); } - internal static SignatureRFC6979 SignRFC6979(this ECDsa key, IMessage message) { return new SignatureRFC6979 @@ -85,7 +84,7 @@ public static class RequestSigner Span result = stackalloc byte[65]; result[0] = 0x04; - key.SignHash(data.Sha512()).AsSpan().CopyTo(result[1..]); + key.SignHash(data.Sha512()).AsSpan().CopyTo(result.Slice(1)); return ByteString.CopyFrom(result); } @@ -100,7 +99,7 @@ public static class RequestSigner Span result = stackalloc byte[65]; result[0] = 0x04; - key.SignHash(hash).AsSpan().CopyTo(result[1..]); + key.SignHash(hash).AsSpan().CopyTo(result.Slice(1)); return ByteString.CopyFrom(result); } diff --git a/src/FrostFS.SDK.Client/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs index ec70d20..c110431 100644 --- a/src/FrostFS.SDK.Client/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -27,8 +27,8 @@ public static class Verifier throw new FormatException($"Wrong signature size, expect={RFC6979SignatureSize}, actual={sig.Length}"); var rs = new BigInteger[2]; - rs[0] = new BigInteger(1, sig[..32]); - rs[1] = new BigInteger(1, sig[32..]); + rs[0] = new BigInteger(1, sig.AsSpan(0, 32).ToArray()); + rs[1] = new BigInteger(1, sig.AsSpan(32).ToArray()); return rs; } @@ -71,7 +71,7 @@ public static class Verifier if (sig is null) throw new ArgumentNullException(nameof(sig)); - return key.VerifyHash(data.Sha512(), sig[1..]); + return key.VerifyHash(data.Sha512(), sig.AsSpan(1).ToArray()); } public static bool VerifyMessagePart(this Signature sig, IMessage data) From f93e33b49b7578ece6edf4e555fac4264269e2a4 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 12 Mar 2025 10:37:12 +0300 Subject: [PATCH 50/65] [#40] Client: Add memory optimization for hash Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/Tools/RequestSigner.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs index 80dfae7..e2aceef 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Security.Cryptography; using FrostFS.Refs; @@ -14,7 +13,6 @@ using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; -using Org.BouncyCastle.Utilities; using Signature = FrostFS.Refs.Signature; namespace FrostFS.SDK.Client; @@ -106,18 +104,7 @@ public static class RequestSigner internal static Signature SignMessagePart(this ClientKey key, IMessage? data) { - if (data is null) - { - return new Signature - { - Key = key.PublicKeyProto, - Sign = key.ECDsaKey.SignData(ReadOnlyMemory.Empty), - }; - } - - var size = data.CalculateSize(); - - if (size == 0) + if (data is null || data.CalculateSize() == 0) { return new Signature { From 98cfd82313d3215b400a5a236b707bbbda8531d6 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 12 Mar 2025 10:44:39 +0300 Subject: [PATCH 51/65] [#40] Client: Add memory optimization for hash: update version Signed-off-by: Pavel Gross --- README.md | 2 +- src/FrostFS.SDK.Client/AssemblyInfo.cs | 2 +- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 6 +----- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 2 +- .../FrostFS.SDK.Cryptography.csproj | 12 ++++-------- src/FrostFS.SDK.Protos/AssemblyInfo.cs | 2 +- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 6 +----- 7 files changed, 10 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index add47ea..b7738b7 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ var createContainerParam = new PrmContainerCreate( var containerId = await client.PutContainerAsync(createContainerParam); -using var fileStream = File.OpenRead(@"C:\Users\Paul\Pictures\cat.jpeg"); +using var fileStream = File.OpenRead(@"cat.jpeg"); var param = new PrmObjectPut { diff --git a/src/FrostFS.SDK.Client/AssemblyInfo.cs b/src/FrostFS.SDK.Client/AssemblyInfo.cs index 61df422..e62079f 100644 --- a/src/FrostFS.SDK.Client/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Client/AssemblyInfo.cs @@ -5,4 +5,4 @@ using System.Reflection; [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Client")] [assembly: AssemblyTitle("FrostFS.SDK.Client")] -[assembly: AssemblyVersion("1.0.2")] +[assembly: AssemblyVersion("1.0.3")] diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 7e1a0bf..5935922 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -24,11 +24,7 @@ - true - - - - Assemblyinfo.cs + false diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index addd4ad..c03d604 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -5,4 +5,4 @@ using System.Reflection; [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] -[assembly: AssemblyVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.0.3.0")] diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index c9a51d3..a4e819e 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -7,23 +7,19 @@ - true + true - <_SkipUpgradeNetAnalyzersNuGetWarning>true + <_SkipUpgradeNetAnalyzersNuGetWarning>true - true + true - true - - - - Assemblyinfo.cs + false diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index eb6f873..11ace79 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -5,4 +5,4 @@ using System.Reflection; [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] -[assembly: AssemblyVersion("1.0.2.0")] +[assembly: AssemblyVersion("1.0.3.0")] diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index cd6627e..d5d8a9e 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -19,11 +19,7 @@ - true - - - - Assemblyinfo.cs + false From 9eb742da77cf0162111ca215d28a7b3285420709 Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Thu, 13 Mar 2025 11:19:34 +0300 Subject: [PATCH 52/65] [#50] ci: Publish NuGet packages at git.frostfs.info Signed-off-by: Vitaliy Potyarkin --- .forgejo/workflows/publish.yml | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .forgejo/workflows/publish.yml diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml new file mode 100644 index 0000000..ca66daa --- /dev/null +++ b/.forgejo/workflows/publish.yml @@ -0,0 +1,36 @@ +on: + push: + workflow_dispatch: + +jobs: + image: + name: Publish NuGet packages + runs-on: docker + container: git.frostfs.info/truecloudlab/env:dotnet-8.0 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Build NuGet packages + # `dotnet build` implies and replaces `dotnet pack` thanks to `GeneratePackageOnBuild` + run: dotnet build + + - name: Publish NuGet packages + run: |- + dotnet nuget add source \ + --name "$NUGET_REGISTRY" \ + --username "$NUGET_REGISTRY_USER" \ + --password "$NUGET_REGISTRY_PASSWORD" \ + --store-password-in-clear-text \ + "$NUGET_REGISTRY_URL" + find -iname '*.nupkg' | grep . | xargs -d'\n' -t -n1 \ + dotnet nuget push --source "$NUGET_REGISTRY" + env: + NUGET_REGISTRY: TrueCloudLab + NUGET_REGISTRY_URL: https://git.frostfs.info/api/packages/TrueCloudLab/nuget/index.json + NUGET_REGISTRY_USER: ${{secrets.NUGET_REGISTRY_USER}} + NUGET_REGISTRY_PASSWORD: ${{secrets.NUGET_REGISTRY_PASSWORD}} + if: >- + startsWith(github.ref, 'refs/tags/v') && + (github.event_name == 'workflow_dispatch' || github.event_name == 'push') From 5e86f53b0e6f6a854f0cee2ab9c720a4ddd2c476 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 13 Mar 2025 12:41:16 +0300 Subject: [PATCH 53/65] [#41] Client: Add attributes for nuget Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 10 ++++++++-- .../FrostFS.SDK.Cryptography.csproj | 10 ++++++++-- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 5935922..2205b7e 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -5,6 +5,12 @@ 12.0 enable AllEnabledByDefault + FrostFS.SDK.Client + 1.0.3 + + C# SDK for FrostFS gRPC native protocol + + true @@ -14,11 +20,11 @@ true - + <_SkipUpgradeNetAnalyzersNuGetWarning>true - + true diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index a4e819e..e4c7824 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -4,16 +4,22 @@ netstandard2.0 12.0 enable + FrostFS.SDK.Cryptography + 1.0.3 + + Cryptography tools for C# SDK + + true true - + <_SkipUpgradeNetAnalyzersNuGetWarning>true - + true diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index d5d8a9e..5207bbc 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -4,16 +4,22 @@ netstandard2.0 12.0 enable + FrostFS.SDK.Protos + 1.0.3 + + Protobuf client for C# SDK + + true true - + <_SkipUpgradeNetAnalyzersNuGetWarning>true - + true From 87fe8db674b1100711e4b9fb906656b8183d3ed5 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 26 Mar 2025 16:51:42 +0300 Subject: [PATCH 54/65] [#43] Client: Memory optimization Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/ApeRules/Actions.cs | 2 +- src/FrostFS.SDK.Client/ApeRules/Condition.cs | 2 +- src/FrostFS.SDK.Client/ApeRules/Resources.cs | 2 +- src/FrostFS.SDK.Client/AssemblyInfo.cs | 4 +- src/FrostFS.SDK.Client/Caches.cs | 9 - .../Exceptions/FrostFsResponseException.cs | 4 +- .../Extensions/FrostFsExtensions.cs | 72 +- .../FrostFS.SDK.Client.csproj | 2 +- src/FrostFS.SDK.Client/FrostFSClient.cs | 35 +- .../Interfaces/IFrostFSClient.cs | 2 - src/FrostFS.SDK.Client/Mappers/ContainerId.cs | 16 +- .../Mappers/Object/ObjectHeaderMapper.cs | 33 +- .../Mappers/Object/ObjectId.cs | 2 +- src/FrostFS.SDK.Client/Mappers/OwnerId.cs | 2 +- .../Mappers/Session/SessionMapper.cs | 28 - .../Mappers/SignatureMapper.cs | 4 +- .../Models/Containers/FrostFsContainerInfo.cs | 5 +- .../Models/Misc/CheckSum.cs | 2 +- .../Models/Netmap/Placement/Context.cs | 2 +- .../Models/Object/FrostFsObject.cs | 23 - .../Models/Object/FrostFsSplit.cs | 26 + .../Models/Object/SplitId.cs | 13 +- .../Models/Response/FrostFsResponseStatus.cs | 4 +- .../Parameters/PrmApeChainRemove.cs | 2 +- .../Pool/ClientStatusMonitor.cs | 163 ----- src/FrostFS.SDK.Client/Pool/ClientWrapper.cs | 135 ---- src/FrostFS.SDK.Client/Pool/HealthyStatus.cs | 18 - src/FrostFS.SDK.Client/Pool/IClientStatus.cs | 28 - src/FrostFS.SDK.Client/Pool/InitParameters.cs | 45 -- src/FrostFS.SDK.Client/Pool/InnerPool.cs | 47 -- src/FrostFS.SDK.Client/Pool/MethodIndex.cs | 24 - src/FrostFS.SDK.Client/Pool/MethodStatus.cs | 19 - src/FrostFS.SDK.Client/Pool/NodeParam.cs | 12 - src/FrostFS.SDK.Client/Pool/NodeStatistic.cs | 12 - src/FrostFS.SDK.Client/Pool/NodesParam.cs | 12 - src/FrostFS.SDK.Client/Pool/Pool.cs | 677 ----------------- .../Pool/RebalanceParameters.cs | 16 - src/FrostFS.SDK.Client/Pool/RequestInfo.cs | 14 - src/FrostFS.SDK.Client/Pool/Sampler.cs | 85 --- src/FrostFS.SDK.Client/Pool/Statistic.cs | 12 - src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs | 8 - src/FrostFS.SDK.Client/Pool/WorkList.cs | 26 - src/FrostFS.SDK.Client/Pool/WrapperPrm.cs | 39 - .../Services/ApeManagerServiceProvider.cs | 2 - .../Services/ContainerServiceProvider.cs | 7 +- .../Services/ObjectServiceProvider.cs | 25 +- .../{Pool => Services/Shared}/SessionCache.cs | 14 +- src/FrostFS.SDK.Client/Tools/ClientContext.cs | 2 +- src/FrostFS.SDK.Client/Tools/ObjectTools.cs | 25 +- src/FrostFS.SDK.Client/Tools/RequestSigner.cs | 22 +- src/FrostFS.SDK.Client/Tools/Verifier.cs | 66 +- src/FrostFS.SDK.Cryptography/ArrayHelper.cs | 14 + src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 4 +- src/FrostFS.SDK.Cryptography/Base58.cs | 32 +- src/FrostFS.SDK.Cryptography/DataHasher.cs | 121 ++++ src/FrostFS.SDK.Cryptography/Extentions.cs | 79 -- .../FrostFS.SDK.Cryptography.csproj | 1 - src/FrostFS.SDK.Cryptography/HashStream.cs | 4 +- src/FrostFS.SDK.Cryptography/Key.cs | 36 +- src/FrostFS.SDK.Protos/AssemblyInfo.cs | 4 +- .../Mocks/AsyncStreamReaderMock.cs | 3 +- .../ContainerServiceBase.cs | 2 - .../ContainerServiceMocks/GetContainerMock.cs | 7 +- src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 1 - .../Client/ContainerTests/ContainerTests.cs | 41 +- .../Smoke/Client/NetworkTests/NetworkTests.cs | 10 +- .../Smoke/Client/ObjectTests/ObjectTests.cs | 6 +- .../Multithread/MultiThreadTestsBase.cs | 42 -- .../Multithread/MultithreadPoolSmokeTests.cs | 544 -------------- .../MultithreadSmokeClientTests.cs | 684 ------------------ .../Smoke/PoolTests/PoolSmokeTests.cs | 546 -------------- src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs | 16 +- src/FrostFS.SDK.Tests/Unit/ContainerTest.cs | 10 +- src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 4 +- src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs | 2 +- src/FrostFS.SDK.Tests/Unit/SessionTests.cs | 3 +- 76 files changed, 399 insertions(+), 3668 deletions(-) delete mode 100644 src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/ClientWrapper.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/HealthyStatus.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/IClientStatus.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/InitParameters.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/InnerPool.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/MethodIndex.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/MethodStatus.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/NodeParam.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/NodeStatistic.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/NodesParam.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/Pool.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/RequestInfo.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/Sampler.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/Statistic.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/WorkList.cs delete mode 100644 src/FrostFS.SDK.Client/Pool/WrapperPrm.cs rename src/FrostFS.SDK.Client/{Pool => Services/Shared}/SessionCache.cs (72%) create mode 100644 src/FrostFS.SDK.Cryptography/DataHasher.cs delete mode 100644 src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs delete mode 100644 src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs delete mode 100644 src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs delete mode 100644 src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs diff --git a/src/FrostFS.SDK.Client/ApeRules/Actions.cs b/src/FrostFS.SDK.Client/ApeRules/Actions.cs index 71888b9..8bae303 100644 --- a/src/FrostFS.SDK.Client/ApeRules/Actions.cs +++ b/src/FrostFS.SDK.Client/ApeRules/Actions.cs @@ -8,7 +8,7 @@ public struct Actions(bool inverted, string[] names) : System.IEquatable public override bool Equals(object obj) { - if (obj == null || obj is not Condition) + if (obj == null || obj is not Condition) return false; return Equals((Condition)obj); diff --git a/src/FrostFS.SDK.Client/ApeRules/Resources.cs b/src/FrostFS.SDK.Client/ApeRules/Resources.cs index ef06b4b..47ff3e1 100644 --- a/src/FrostFS.SDK.Client/ApeRules/Resources.cs +++ b/src/FrostFS.SDK.Client/ApeRules/Resources.cs @@ -8,7 +8,7 @@ public struct Resources(bool inverted, string[] names) : System.IEquatable _ownersCache; - - internal static IMemoryCache Containers => _containersCache; } diff --git a/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs index 0e64db5..87799e2 100644 --- a/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs +++ b/src/FrostFS.SDK.Client/Exceptions/FrostFsResponseException.cs @@ -10,8 +10,8 @@ public class FrostFsResponseException : FrostFsException { } - public FrostFsResponseException(FrostFsResponseStatus status) - : base(status != null ? status.Message != null ? "" : "" : "") + public FrostFsResponseException(FrostFsResponseStatus status) + : base(status != null ? status.Message != null ? "" : "" : "") { Status = status; } diff --git a/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs b/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs index a986a87..87728d5 100644 --- a/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs +++ b/src/FrostFS.SDK.Client/Extensions/FrostFsExtensions.cs @@ -1,14 +1,17 @@ using System; - +using System.Security.Cryptography; using Google.Protobuf; namespace FrostFS.SDK.Cryptography; public static class FrostFsExtensions { - public static ByteString Sha256(this IMessage data) + public static byte[] Sha256(this IMessage data) { - return ByteString.CopyFrom(data.ToByteArray().Sha256()); + using var sha256 = SHA256.Create(); + using HashStream stream = new(sha256); + data.WriteTo(stream); + return stream.Hash(); } public static Guid ToUuid(this ByteString id) @@ -16,9 +19,18 @@ public static class FrostFsExtensions if (id == null) throw new ArgumentNullException(nameof(id)); - var orderedBytes = GetGuidBytesDirectOrder(id.Span); - - return new Guid(orderedBytes); + return new Guid( + (id[0] << 24) + (id[1] << 16) + (id[2] << 8) + id[3], + (short)((id[4] << 8) + id[5]), + (short)((id[6] << 8) + id[7]), + id[8], + id[9], + id[10], + id[11], + id[12], + id[13], + id[14], + id[15]); } /// @@ -26,37 +38,25 @@ public static class FrostFsExtensions /// /// /// - public static byte[] ToBytes(this Guid id) + public unsafe static void ToBytes(this Guid id, Span span) { - var bytes = id.ToByteArray(); + var pGuid = (byte*)&id; - var orderedBytes = GetGuidBytesDirectOrder(bytes); - - return orderedBytes; - } - - private static byte[] GetGuidBytesDirectOrder(ReadOnlySpan 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] - ]; + span[0] = pGuid[3]; + span[1] = pGuid[2]; + span[2] = pGuid[1]; + span[3] = pGuid[0]; + span[4] = pGuid[5]; + span[5] = pGuid[4]; + span[6] = pGuid[7]; + span[7] = pGuid[6]; + span[8] = pGuid[8]; + span[9] = pGuid[9]; + span[10] = pGuid[10]; + span[11] = pGuid[11]; + span[12] = pGuid[12]; + span[13] = pGuid[13]; + span[14] = pGuid[14]; + span[15] = pGuid[15]; } } diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 2205b7e..e94d3f8 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -31,10 +31,10 @@ false + True - all diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index f72265c..860d346 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -162,25 +162,7 @@ public class FrostFSClient : IFrostFSClient Callback = settings.Value.Callback, Interceptors = settings.Value.Interceptors }; - - // TODO: define timeout logic - // CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) }); - } - - internal FrostFSClient(WrapperPrm prm, SessionCache cache) - { - ClientCtx = new ClientContext( - client: this, - key: new ClientKey(prm.Key), - owner: FrostFsOwner.FromKey(prm.Key!), - channel: prm.GrpcChannelFactory(prm.Address), - version: new FrostFsVersion(2, 13)) - { - SessionCache = cache, - Interceptors = prm.Interceptors, - Callback = prm.Callback - }; - } + } #region ApeManagerImplementation public Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) @@ -447,18 +429,5 @@ public class FrostFSClient : IFrostFSClient } return ObjectServiceProvider; - } - - public async Task Dial(CallContext ctx) - { - var service = GetAccouningService(); - _ = await service.GetBallance(ctx).ConfigureAwait(false); - - return null; - } - - public bool RestartIfUnhealthy(CallContext ctx) - { - throw new NotImplementedException(); - } + } } diff --git a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs index d546156..43dc3ac 100644 --- a/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs +++ b/src/FrostFS.SDK.Client/Interfaces/IFrostFSClient.cs @@ -64,6 +64,4 @@ public interface IFrostFSClient #region Account Task GetBalanceAsync(CallContext ctx); #endregion - - public Task Dial(CallContext ctx); } diff --git a/src/FrostFS.SDK.Client/Mappers/ContainerId.cs b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs index df27320..55b6179 100644 --- a/src/FrostFS.SDK.Client/Mappers/ContainerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/ContainerId.cs @@ -5,16 +5,10 @@ using FrostFS.SDK.Cryptography; using Google.Protobuf; -using Microsoft.Extensions.Caching.Memory; - namespace FrostFS.SDK.Client.Mappers.GRPC; public static class ContainerIdMapper { - private static readonly MemoryCacheEntryOptions _oneHourExpiration = new MemoryCacheEntryOptions() - .SetSlidingExpiration(TimeSpan.FromHours(1)) - .SetSize(1); - public static ContainerID ToMessage(this FrostFsContainerId model) { if (model is null) @@ -24,15 +18,11 @@ public static class ContainerIdMapper var containerId = model.GetValue() ?? throw new ArgumentNullException(nameof(model)); - if (!Caches.Containers.TryGetValue(containerId, out ContainerID? message)) + var message = new ContainerID { - message = new ContainerID - { - Value = ByteString.CopyFrom(Base58.Decode(containerId)) - }; + Value = UnsafeByteOperations.UnsafeWrap(Base58.Decode(containerId)) + }; - Caches.Containers.Set(containerId, message, _oneHourExpiration); - } return message!; } diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs index 317b31c..115f9e2 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs @@ -24,25 +24,14 @@ public static class ObjectHeaderMapper _ => throw new ArgumentException($"Unknown ObjectType. Value: '{header.ObjectType}'.") }; - FrostFsSplit? split = null; - - if (header.Split != null) - { - var children = header.Split.Children.Count != 0 ? new ReadOnlyCollection( - header.Split.Children.Select(x => x.ToModel()).ToList()) : null; - - split = new FrostFsSplit(new SplitId(header.Split.SplitId.ToUuid()), - header.Split.Previous?.ToModel(), - header.Split.Parent?.ToModel(), - header.Split.ParentHeader?.ToModel(), - null, - children); - } + FrostFsSplit? split = header!.Split != null + ? header.Split.ToModel() + : null; var model = new FrostFsObjectHeader( new FrostFsContainerId(Base58.Encode(header.ContainerId.Value.Span)), objTypeName, - header.Attributes.Select(attribute => attribute.ToModel()).ToArray(), + [.. header.Attributes.Select(attribute => attribute.ToModel())], split, header.OwnerId.ToModel(), header.Version.ToModel()) @@ -52,4 +41,18 @@ public static class ObjectHeaderMapper return model; } + + public static FrostFsSplit ToModel(this Header.Types.Split split) + { + var children = split!.Children.Count != 0 + ? new ReadOnlyCollection([.. split.Children.Select(x => x.ToModel())]) + : null; + + return new FrostFsSplit(new SplitId(split.SplitId.ToUuid()), + split.Previous?.ToModel(), + split.Parent?.ToModel(), + split.ParentHeader?.ToModel(), + null, + children); + } } diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs index 343e3de..5562be5 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectId.cs @@ -17,7 +17,7 @@ public static class ObjectIdMapper return new ObjectID { - Value = ByteString.CopyFrom(objectId.ToHash()) + Value = UnsafeByteOperations.UnsafeWrap(objectId.ToHash()) }; } diff --git a/src/FrostFS.SDK.Client/Mappers/OwnerId.cs b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs index 6739a0b..54f7a21 100644 --- a/src/FrostFS.SDK.Client/Mappers/OwnerId.cs +++ b/src/FrostFS.SDK.Client/Mappers/OwnerId.cs @@ -26,7 +26,7 @@ public static class OwnerIdMapper { message = new OwnerID { - Value = ByteString.CopyFrom(model.ToHash()) + Value = UnsafeByteOperations.UnsafeWrap(model.ToHash()) }; Caches.Owners.Set(model, message, _oneHourExpiration); diff --git a/src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs b/src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs deleted file mode 100644 index d1307e8..0000000 --- a/src/FrostFS.SDK.Client/Mappers/Session/SessionMapper.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -using Google.Protobuf; - -namespace FrostFS.SDK.Client; - -public static class SessionMapper -{ - public static byte[] Serialize(this Session.SessionToken token) - { - if (token is null) - { - throw new ArgumentNullException(nameof(token)); - } - - byte[] bytes = new byte[token.CalculateSize()]; - using CodedOutputStream stream = new(bytes); - token.WriteTo(stream); - - return bytes; - } - - public static Session.SessionToken Deserialize(this Session.SessionToken token, byte[] bytes) - { - token.MergeFrom(bytes); - return token; - } -} diff --git a/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs b/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs index 88b4ac8..ffae746 100644 --- a/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/SignatureMapper.cs @@ -23,9 +23,9 @@ public static class SignatureMapper return new Refs.Signature { - Key = ByteString.CopyFrom(signature.Key), + Key = UnsafeByteOperations.UnsafeWrap(signature.Key), Scheme = scheme, - Sign = ByteString.CopyFrom(signature.Sign) + Sign = UnsafeByteOperations.UnsafeWrap(signature.Sign) }; } } diff --git a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs index af4ffdc..c7dc782 100644 --- a/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Containers/FrostFsContainerInfo.cs @@ -91,10 +91,13 @@ public class FrostFsContainerInfo throw new ArgumentNullException("PlacementPolicy is null"); } + Span nonce = stackalloc byte[16]; + Nonce.ToBytes(nonce); + this.container = new Container.Container() { PlacementPolicy = PlacementPolicy.Value.GetPolicy(), - Nonce = ByteString.CopyFrom(Nonce.ToBytes()), + Nonce = ByteString.CopyFrom(nonce), OwnerId = Owner?.OwnerID, Version = Version?.VersionID }; diff --git a/src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs b/src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs index fdeac99..2241227 100644 --- a/src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs +++ b/src/FrostFS.SDK.Client/Models/Misc/CheckSum.cs @@ -11,7 +11,7 @@ public class CheckSum public static CheckSum CreateCheckSum(byte[] content) { - return new CheckSum { hash = content.Sha256() }; + return new CheckSum { hash = DataHasher.Sha256(content.AsSpan()) }; } public override string ToString() diff --git a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs index cd287d0..aedfd35 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/Placement/Context.cs @@ -353,7 +353,7 @@ internal struct Context var start = hasPrefix ? likeWildcard.Length : 0; var end = hasSuffix ? f.Value.Length - likeWildcard.Length : f.Value.Length; - var str = f.Value.Substring(start, end-start); + var str = f.Value.Substring(start, end - start); if (hasPrefix && hasSuffix) return nodeInfo.Attributes[f.Key].Contains(str); diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs index c39a587..e22016a 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsObject.cs @@ -4,10 +4,6 @@ namespace FrostFS.SDK; public class FrostFsObject { - // private byte[]? _payloadBytes; - // private ReadOnlyMemory _payloadMemory; - // private bool _isInitPayloadMemory; - /// /// Creates new instance from ObjectHeader /// @@ -49,25 +45,6 @@ public class FrostFsObject public ReadOnlyMemory SingleObjectPayload { get; set; } - //public ReadOnlyMemory SingleObjectPayloadMemory - //{ - // get - // { - // if (!_isInitPayloadMemory) - // { - // _payloadMemory = _payloadBytes.AsMemory(); - // _isInitPayloadMemory = true; - // } - - // return _payloadMemory; - // } - // set - // { - // _payloadMemory = value; - // _isInitPayloadMemory = true; - // } - //} - /// /// Provide SHA256 hash of the payload. If null, the hash is calculated by internal logic /// diff --git a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs index bd850cb..5afd46b 100644 --- a/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs +++ b/src/FrostFS.SDK.Client/Models/Object/FrostFsSplit.cs @@ -1,4 +1,7 @@ using System.Collections.ObjectModel; +using System.Linq; +using FrostFS.Object; +using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK; @@ -9,6 +12,8 @@ public class FrostFsSplit(SplitId splitId, FrostFsSignature? parentSignature = null, ReadOnlyCollection? children = null) { + private Header.Types.Split? _split; + public FrostFsSplit() : this(new SplitId()) { } @@ -24,4 +29,25 @@ public class FrostFsSplit(SplitId splitId, public FrostFsObjectHeader? ParentHeader { get; set; } = parentHeader; public ReadOnlyCollection? Children { get; } = children; + + public Header.Types.Split GetSplit() + { + if (_split == null) + { + _split = new Header.Types.Split + { + SplitId = SplitId?.GetSplitId(), + Parent = Parent?.ToMessage(), + ParentHeader = ParentHeader?.GetHeader(), + ParentSignature = ParentSignature?.ToMessage() + }; + + if (Children != null) + { + _split.Children.AddRange(Children.Select(x => x.ToMessage())); + } + } + + return _split; + } } diff --git a/src/FrostFS.SDK.Client/Models/Object/SplitId.cs b/src/FrostFS.SDK.Client/Models/Object/SplitId.cs index 177817e..a113361 100644 --- a/src/FrostFS.SDK.Client/Models/Object/SplitId.cs +++ b/src/FrostFS.SDK.Client/Models/Object/SplitId.cs @@ -47,16 +47,11 @@ public class SplitId return this.id.ToString(); } - public byte[]? ToBinary() - { - if (this.id == Guid.Empty) - return null; - - return this.id.ToBytes(); - } - public ByteString? GetSplitId() { - return this.message ??= ByteString.CopyFrom(ToBinary()); + Span span = stackalloc byte[16]; + id.ToBytes(span); + + return this.message ??= ByteString.CopyFrom(span); } } diff --git a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs index 2f48ba3..9e1a846 100644 --- a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs +++ b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs @@ -4,9 +4,9 @@ public class FrostFsResponseStatus(FrostFsStatusCode code, string? message = nul { 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() diff --git a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs index 720c3c4..2303776 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmApeChainRemove.cs @@ -25,7 +25,7 @@ public readonly struct PrmApeChainRemove( public readonly bool Equals(PrmApeChainRemove other) { return Target == other.Target - && ChainId.Equals(other.ChainId) + && ChainId.Equals(other.ChainId) && XHeaders == other.XHeaders; } diff --git a/src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs b/src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs deleted file mode 100644 index c05c24c..0000000 --- a/src/FrostFS.SDK.Client/Pool/ClientStatusMonitor.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.Threading; - -using Microsoft.Extensions.Logging; - -namespace FrostFS.SDK.Client; - -// clientStatusMonitor count error rate and other statistics for connection. -public class ClientStatusMonitor : IClientStatus -{ - private static readonly MethodIndex[] MethodIndexes = - [ - MethodIndex.methodBalanceGet, - MethodIndex.methodContainerPut, - MethodIndex.methodContainerGet, - MethodIndex.methodContainerList, - MethodIndex.methodContainerDelete, - MethodIndex.methodEndpointInfo, - MethodIndex.methodNetworkInfo, - MethodIndex.methodNetMapSnapshot, - MethodIndex.methodObjectPut, - MethodIndex.methodObjectDelete, - MethodIndex.methodObjectGet, - MethodIndex.methodObjectHead, - MethodIndex.methodObjectRange, - MethodIndex.methodObjectPatch, - MethodIndex.methodSessionCreate, - MethodIndex.methodAPEManagerAddChain, - MethodIndex.methodAPEManagerRemoveChain, - MethodIndex.methodAPEManagerListChains - ]; - - public static string GetMethodName(MethodIndex index) - { - return index switch - { - MethodIndex.methodBalanceGet => "BalanceGet", - MethodIndex.methodContainerPut => "ContainerPut", - MethodIndex.methodContainerGet => "ContainerGet", - MethodIndex.methodContainerList => "ContainerList", - MethodIndex.methodContainerDelete => "ContainerDelete", - MethodIndex.methodEndpointInfo => "EndpointInfo", - MethodIndex.methodNetworkInfo => "NetworkInfo", - MethodIndex.methodNetMapSnapshot => "NetMapSnapshot", - MethodIndex.methodObjectPut => "ObjectPut", - MethodIndex.methodObjectDelete => "ObjectDelete", - MethodIndex.methodObjectGet => "ObjectGet", - MethodIndex.methodObjectHead => "ObjectHead", - MethodIndex.methodObjectRange => "ObjectRange", - MethodIndex.methodObjectPatch => "ObjectPatch", - MethodIndex.methodSessionCreate => "SessionCreate", - MethodIndex.methodAPEManagerAddChain => "APEManagerAddChain", - MethodIndex.methodAPEManagerRemoveChain => "APEManagerRemoveChain", - MethodIndex.methodAPEManagerListChains => "APEManagerListChains", - _ => throw new ArgumentException("Unknown method", nameof(index)), - }; - } - - private readonly object _lock = new(); - - private readonly ILogger? logger; - private int healthy; - - public ClientStatusMonitor(ILogger? logger, string address) - { - this.logger = logger; - healthy = (int)HealthyStatus.Healthy; - - Address = address; - Methods = new MethodStatus[MethodIndexes.Length]; - - for (int i = 0; i < MethodIndexes.Length; i++) - { - Methods[i] = new MethodStatus(GetMethodName(MethodIndexes[i])); - } - } - - public string Address { get; } - - internal uint ErrorThreshold { get; set; } - - public uint CurrentErrorCount { get; set; } - - public ulong OverallErrorCount { get; set; } - - public MethodStatus[] Methods { get; private set; } - - public bool IsHealthy() - { - var res = Interlocked.CompareExchange(ref healthy, -1, -1) == (int)HealthyStatus.Healthy; - return res; - } - - public bool IsDialed() - { - return Interlocked.CompareExchange(ref healthy, -1, -1) != (int)HealthyStatus.UnhealthyOnDial; - } - - public void SetHealthy() - { - Interlocked.Exchange(ref healthy, (int)HealthyStatus.Healthy); - } - public void SetUnhealthy() - { - Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnRequest); - } - - public void SetUnhealthyOnDial() - { - Interlocked.Exchange(ref healthy, (int)HealthyStatus.UnhealthyOnDial); - } - - public void IncErrorRate() - { - bool thresholdReached; - lock (_lock) - { - CurrentErrorCount++; - OverallErrorCount++; - - thresholdReached = CurrentErrorCount >= ErrorThreshold; - - if (thresholdReached) - { - SetUnhealthy(); - CurrentErrorCount = 0; - } - } - - if (thresholdReached && logger != null) - { - FrostFsMessages.ErrorЕhresholdReached(logger, Address, ErrorThreshold); - } - } - - public uint GetCurrentErrorRate() - { - lock (_lock) - { - return CurrentErrorCount; - } - } - - public ulong GetOverallErrorRate() - { - lock (_lock) - { - return OverallErrorCount; - } - } - - public StatusSnapshot[] MethodsStatus() - { - var result = new StatusSnapshot[Methods.Length]; - - for (int i = 0; i < result.Length; i++) - { - result[i] = Methods[i].Snapshot!; - } - - return result; - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs b/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs deleted file mode 100644 index 552ee2a..0000000 --- a/src/FrostFS.SDK.Client/Pool/ClientWrapper.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Threading.Tasks; - -using Grpc.Core; - -namespace FrostFS.SDK.Client; - -// clientWrapper is used by default, alternative implementations are intended for testing purposes only. -public class ClientWrapper : ClientStatusMonitor -{ - private readonly object _lock = new(); - private SessionCache sessionCache; - - internal ClientWrapper(WrapperPrm wrapperPrm, Pool pool) : base(wrapperPrm.Logger, wrapperPrm.Address) - { - WrapperPrm = wrapperPrm; - ErrorThreshold = wrapperPrm.ErrorThreshold; - - sessionCache = pool.SessionCache; - Client = new FrostFSClient(WrapperPrm, sessionCache); - } - - internal FrostFSClient? Client { get; private set; } - - internal WrapperPrm WrapperPrm { get; } - - internal FrostFSClient? GetClient() - { - lock (_lock) - { - if (IsHealthy()) - { - return Client; - } - - return null; - } - } - - // dial establishes a connection to the server from the FrostFS network. - // Returns an error describing failure reason. If failed, the client - // SHOULD NOT be used. - internal async Task Dial(CallContext ctx) - { - var client = GetClient() ?? throw new FrostFsInvalidObjectException("pool client unhealthy"); - - await client.Dial(ctx).ConfigureAwait(false); - } - - internal void HandleError(Exception ex) - { - if (ex is FrostFsResponseException responseException && responseException.Status != null) - { - switch (responseException.Status.Code) - { - case FrostFsStatusCode.Internal: - case FrostFsStatusCode.WrongMagicNumber: - case FrostFsStatusCode.SignatureVerificationFailure: - case FrostFsStatusCode.NodeUnderMaintenance: - IncErrorRate(); - return; - } - } - - IncErrorRate(); - } - - private async Task ScheduleGracefulClose() - { - if (Client == null) - return; - - await Task.Delay((int)WrapperPrm.GracefulCloseOnSwitchTimeout).ConfigureAwait(false); - } - - // restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy. - // Indicating if status was changed by this function call and returns error that caused unhealthy status. - internal async Task RestartIfUnhealthy(CallContext ctx) - { - bool wasHealthy; - - try - { - var response = await Client!.GetNodeInfoAsync(ctx).ConfigureAwait(false); - return false; - } - catch (RpcException) - { - wasHealthy = true; - } - - // if connection is dialed before, to avoid routine/connection leak, - // pool has to close it and then initialize once again. - if (IsDialed()) - { - await ScheduleGracefulClose().ConfigureAwait(false); - } - - FrostFSClient? client = new(WrapperPrm, sessionCache); - - var error = await client.Dial(ctx).ConfigureAwait(false); - if (!string.IsNullOrEmpty(error)) - { - SetUnhealthyOnDial(); - return wasHealthy; - } - - lock (_lock) - { - Client = client; - } - - try - { - var res = await Client.GetNodeInfoAsync(ctx).ConfigureAwait(false); - } - catch (FrostFsException) - { - SetUnhealthy(); - - return wasHealthy; - } - - SetHealthy(); - return !wasHealthy; - } - - internal void IncRequests(ulong elapsed, MethodIndex method) - { - var methodStat = Methods[(int)method]; - - methodStat.IncRequests(elapsed); - } -} - diff --git a/src/FrostFS.SDK.Client/Pool/HealthyStatus.cs b/src/FrostFS.SDK.Client/Pool/HealthyStatus.cs deleted file mode 100644 index d02e6bb..0000000 --- a/src/FrostFS.SDK.Client/Pool/HealthyStatus.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FrostFS.SDK.Client; - -// values for healthy status of clientStatusMonitor. -public enum HealthyStatus -{ - // statusUnhealthyOnDial is set when dialing to the endpoint is failed, - // so there is no connection to the endpoint, and pool should not close it - // before re-establishing connection once again. - UnhealthyOnDial, - - // statusUnhealthyOnRequest is set when communication after dialing to the - // endpoint is failed due to immediate or accumulated errors, connection is - // available and pool should close it before re-establishing connection once again. - UnhealthyOnRequest, - - // statusHealthy is set when connection is ready to be used by the pool. - Healthy -} diff --git a/src/FrostFS.SDK.Client/Pool/IClientStatus.cs b/src/FrostFS.SDK.Client/Pool/IClientStatus.cs deleted file mode 100644 index 0c08fac..0000000 --- a/src/FrostFS.SDK.Client/Pool/IClientStatus.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace FrostFS.SDK.Client; - -public interface IClientStatus -{ - // isHealthy checks if the connection can handle requests. - bool IsHealthy(); - - // isDialed checks if the connection was created. - bool IsDialed(); - - // setUnhealthy marks client as unhealthy. - void SetUnhealthy(); - - // address return address of endpoint. - string Address { get; } - - // currentErrorRate returns current errors rate. - // After specific threshold connection is considered as unhealthy. - // Pool.startRebalance routine can make this connection healthy again. - uint GetCurrentErrorRate(); - - // overallErrorRate returns the number of all happened errors. - ulong GetOverallErrorRate(); - - // methodsStatus returns statistic for all used methods. - StatusSnapshot[] MethodsStatus(); -} - diff --git a/src/FrostFS.SDK.Client/Pool/InitParameters.cs b/src/FrostFS.SDK.Client/Pool/InitParameters.cs deleted file mode 100644 index 66da23f..0000000 --- a/src/FrostFS.SDK.Client/Pool/InitParameters.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Security.Cryptography; - -using Grpc.Core; -using Grpc.Core.Interceptors; -using Grpc.Net.Client; - -using Microsoft.Extensions.Logging; - -namespace FrostFS.SDK.Client; - -// InitParameters contains values used to initialize connection Pool. -public class InitParameters(Func grpcChannelFactory) -{ - public ECDsa? Key { get; set; } - - public ulong NodeDialTimeout { get; set; } - - public ulong NodeStreamTimeout { get; set; } - - public ulong HealthcheckTimeout { get; set; } - - public ulong ClientRebalanceInterval { get; set; } - - public ulong SessionExpirationDuration { get; set; } - - public uint ErrorThreshold { get; set; } - - public NodeParam[]? NodeParams { get; set; } - - public GrpcChannelOptions[]? DialOptions { get; set; } - - public Func? ClientBuilder { get; set; } - - public ulong GracefulCloseOnSwitchTimeout { get; set; } - - public ILogger? Logger { get; set; } - - public Action? Callback { get; set; } - - public Collection Interceptors { get; } = []; - - public Func GrpcChannelFactory { get; set; } = grpcChannelFactory; -} diff --git a/src/FrostFS.SDK.Client/Pool/InnerPool.cs b/src/FrostFS.SDK.Client/Pool/InnerPool.cs deleted file mode 100644 index f712552..0000000 --- a/src/FrostFS.SDK.Client/Pool/InnerPool.cs +++ /dev/null @@ -1,47 +0,0 @@ -using FrostFS.SDK.Client; - -internal sealed class InnerPool -{ - private readonly object _lock = new(); - - internal InnerPool(Sampler sampler, ClientWrapper[] clients) - { - Clients = clients; - Sampler = sampler; - } - - internal Sampler Sampler { get; set; } - - internal ClientWrapper[] Clients { get; } - - internal ClientWrapper? Connection() - { - lock (_lock) - { - if (Clients.Length == 1) - { - var client = Clients[0]; - if (client.IsHealthy()) - { - return client; - } - } - else - { - var attempts = 3 * Clients.Length; - - for (int i = 0; i < attempts; i++) - { - int index = Sampler.Next(); - - if (Clients[index].IsHealthy()) - { - return Clients[index]; - } - } - } - - return null; - } - } -} diff --git a/src/FrostFS.SDK.Client/Pool/MethodIndex.cs b/src/FrostFS.SDK.Client/Pool/MethodIndex.cs deleted file mode 100644 index 53e5430..0000000 --- a/src/FrostFS.SDK.Client/Pool/MethodIndex.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace FrostFS.SDK.Client; - -public enum MethodIndex -{ - methodBalanceGet, - methodContainerPut, - methodContainerGet, - methodContainerList, - methodContainerDelete, - methodEndpointInfo, - methodNetworkInfo, - methodNetMapSnapshot, - methodObjectPut, - methodObjectDelete, - methodObjectGet, - methodObjectHead, - methodObjectRange, - methodObjectPatch, - methodSessionCreate, - methodAPEManagerAddChain, - methodAPEManagerRemoveChain, - methodAPEManagerListChains, - methodLast -} diff --git a/src/FrostFS.SDK.Client/Pool/MethodStatus.cs b/src/FrostFS.SDK.Client/Pool/MethodStatus.cs deleted file mode 100644 index 8f40f3c..0000000 --- a/src/FrostFS.SDK.Client/Pool/MethodStatus.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace FrostFS.SDK.Client; - -public class MethodStatus(string name) -{ - private readonly object _lock = new(); - - public string Name { get; } = name; - - public StatusSnapshot Snapshot { get; set; } = new StatusSnapshot(); - - internal void IncRequests(ulong elapsed) - { - lock (_lock) - { - Snapshot.AllTime += elapsed; - Snapshot.AllRequests++; - } - } -} diff --git a/src/FrostFS.SDK.Client/Pool/NodeParam.cs b/src/FrostFS.SDK.Client/Pool/NodeParam.cs deleted file mode 100644 index 92c2560..0000000 --- a/src/FrostFS.SDK.Client/Pool/NodeParam.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace FrostFS.SDK.Client; - -// NodeParam groups parameters of remote node. -[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] -public readonly struct NodeParam(int priority, string address, float weight) -{ - public int Priority { get; } = priority; - - public string Address { get; } = address; - - public float Weight { get; } = weight; -} diff --git a/src/FrostFS.SDK.Client/Pool/NodeStatistic.cs b/src/FrostFS.SDK.Client/Pool/NodeStatistic.cs deleted file mode 100644 index 9323d93..0000000 --- a/src/FrostFS.SDK.Client/Pool/NodeStatistic.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace FrostFS.SDK.Client; - -public class NodeStatistic -{ - public string? Address { get; internal set; } - - public StatusSnapshot[]? Methods { get; internal set; } - - public ulong OverallErrors { get; internal set; } - - public uint CurrentErrors { get; internal set; } -} diff --git a/src/FrostFS.SDK.Client/Pool/NodesParam.cs b/src/FrostFS.SDK.Client/Pool/NodesParam.cs deleted file mode 100644 index be9f012..0000000 --- a/src/FrostFS.SDK.Client/Pool/NodesParam.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.ObjectModel; - -namespace FrostFS.SDK.Client; - -public class NodesParam(int priority) -{ - public int Priority { get; } = priority; - - public Collection Addresses { get; } = []; - - public Collection Weights { get; } = []; -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Pool/Pool.cs b/src/FrostFS.SDK.Client/Pool/Pool.cs deleted file mode 100644 index cd09d30..0000000 --- a/src/FrostFS.SDK.Client/Pool/Pool.cs +++ /dev/null @@ -1,677 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; - -using FrostFS.Refs; -using FrostFS.SDK.Client.Interfaces; -using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; - -using Grpc.Core; - -using Microsoft.Extensions.Logging; - - -namespace FrostFS.SDK.Client; - -public partial class Pool : IFrostFSClient -{ - const int defaultSessionTokenExpirationDuration = 100; // in epochs - - const int defaultErrorThreshold = 100; - - const int defaultGracefulCloseOnSwitchTimeout = 10; //Seconds; - const int defaultRebalanceInterval = 15; //Seconds; - const int defaultHealthcheckTimeout = 4; //Seconds; - const int defaultDialTimeout = 5; //Seconds; - const int defaultStreamTimeout = 10; //Seconds; - - private readonly object _lock = new(); - - private InnerPool[]? InnerPools { get; set; } - - private ClientKey Key { get; set; } - - private OwnerID? _ownerId; - - private FrostFsVersion _version; - - private FrostFsOwner? _owner; - - private FrostFsOwner Owner - { - get - { - _owner ??= new FrostFsOwner(Key.ECDsaKey.PublicKey().PublicKeyToAddress()); - return _owner; - } - } - - private OwnerID OwnerId - { - get - { - if (_ownerId == null) - { - _owner = Key.Owner; - _ownerId = _owner.ToMessage(); - } - return _ownerId; - } - } - - internal CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); - - internal SessionCache SessionCache { get; set; } - - private ulong SessionTokenDuration { get; set; } - - private RebalanceParameters RebalanceParams { get; set; } - - private Func ClientBuilder; - - private bool disposedValue; - - private ILogger? logger { get; set; } - - private ulong MaxObjectSize { get; set; } - - public IClientStatus? ClientStatus { get; } - - // NewPool creates connection pool using parameters. - public Pool(InitParameters options) - { - if (options is null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (options.Key == null) - { - throw new ArgumentException($"Missed required parameter {nameof(options.Key)}"); - } - - _version = new FrostFsVersion(2, 13); - - var nodesParams = AdjustNodeParams(options.NodeParams); - - var cache = new SessionCache(options.SessionExpirationDuration); - - FillDefaultInitParams(options, this); - - Key = new ClientKey(options.Key); - - SessionCache = cache; - logger = options.Logger; - SessionTokenDuration = options.SessionExpirationDuration; - - RebalanceParams = new RebalanceParameters( - nodesParams.ToArray(), - options.HealthcheckTimeout, - options.ClientRebalanceInterval, - options.SessionExpirationDuration); - - ClientBuilder = options.ClientBuilder!; - - // ClientContext.PoolErrorHandler = client.HandleError; - - } - - // Dial establishes a connection to the servers from the FrostFS network. - // It also starts a routine that checks the health of the nodes and - // updates the weights of the nodes for balancing. - // Returns an error describing failure reason. - // - // If failed, the Pool SHOULD NOT be used. - // - // See also InitParameters.SetClientRebalanceInterval. - public async Task Dial(CallContext ctx) - { - var inner = new InnerPool[RebalanceParams.NodesParams.Length]; - - bool atLeastOneHealthy = false; - int i = 0; - foreach (var nodeParams in RebalanceParams.NodesParams) - { - var wrappers = new ClientWrapper[nodeParams.Weights.Count]; - - for (int j = 0; j < nodeParams.Addresses.Count; j++) - { - ClientWrapper? wrapper = null; - bool dialed = false; - try - { - wrapper = wrappers[j] = ClientBuilder(nodeParams.Addresses[j]); - - await wrapper.Dial(ctx).ConfigureAwait(false); - dialed = true; - - var token = await InitSessionForDuration(ctx, wrapper, RebalanceParams.SessionExpirationDuration, Key.ECDsaKey, false) - .ConfigureAwait(false); - - var key = FormCacheKey(nodeParams.Addresses[j], Key.PublicKey); - SessionCache.SetValue(key, token); - - atLeastOneHealthy = true; - } - catch (RpcException ex) - { - if (!dialed) - wrapper!.SetUnhealthyOnDial(); - else - wrapper!.SetUnhealthy(); - - if (logger != null) - { - FrostFsMessages.SessionCreationError(logger, wrapper!.WrapperPrm.Address, ex.Message); - } - } - catch (FrostFsInvalidObjectException) - { - break; - } - } - - var sampler = new Sampler(nodeParams.Weights.ToArray()); - - inner[i] = new InnerPool(sampler, wrappers); - - i++; - } - - if (!atLeastOneHealthy) - return "At least one node must be healthy"; - - InnerPools = inner; - - var res = await GetNetworkSettingsAsync(default).ConfigureAwait(false); - - MaxObjectSize = res.MaxObjectSize; - - StartRebalance(ctx); - - return null; - } - - private static IEnumerable AdjustNodeParams(NodeParam[]? nodeParams) - { - if (nodeParams == null || nodeParams.Length == 0) - { - throw new ArgumentException("No FrostFS peers configured"); - } - - Dictionary nodesParamsDict = new(nodeParams.Length); - foreach (var nodeParam in nodeParams) - { - if (!nodesParamsDict.TryGetValue(nodeParam.Priority, out var nodes)) - { - nodes = new NodesParam(nodeParam.Priority); - nodesParamsDict[nodeParam.Priority] = nodes; - } - - nodes.Addresses.Add(nodeParam.Address); - nodes.Weights.Add(nodeParam.Weight); - } - - var nodesParams = new List(nodesParamsDict.Count); - - foreach (var key in nodesParamsDict.Keys) - { - var nodes = nodesParamsDict[key]; - var newWeights = AdjustWeights([.. nodes.Weights]); - nodes.Weights.Clear(); - foreach (var weight in newWeights) - { - nodes.Weights.Add(weight); - } - - nodesParams.Add(nodes); - } - - return nodesParams.OrderBy(n => n.Priority); - } - - private static double[] AdjustWeights(double[] weights) - { - var adjusted = new double[weights.Length]; - - var sum = weights.Sum(); - - if (sum > 0) - { - for (int i = 0; i < weights.Length; i++) - { - adjusted[i] = weights[i] / sum; - } - } - - return adjusted; - } - - private static void FillDefaultInitParams(InitParameters parameters, Pool pool) - { - if (parameters.SessionExpirationDuration == 0) - parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration; - - if (parameters.ErrorThreshold == 0) - parameters.ErrorThreshold = defaultErrorThreshold; - - if (parameters.ClientRebalanceInterval <= 0) - parameters.ClientRebalanceInterval = defaultRebalanceInterval; - - if (parameters.GracefulCloseOnSwitchTimeout <= 0) - parameters.GracefulCloseOnSwitchTimeout = defaultGracefulCloseOnSwitchTimeout; - - if (parameters.HealthcheckTimeout <= 0) - parameters.HealthcheckTimeout = defaultHealthcheckTimeout; - - if (parameters.NodeDialTimeout <= 0) - parameters.NodeDialTimeout = defaultDialTimeout; - - if (parameters.NodeStreamTimeout <= 0) - parameters.NodeStreamTimeout = defaultStreamTimeout; - - if (parameters.SessionExpirationDuration == 0) - parameters.SessionExpirationDuration = defaultSessionTokenExpirationDuration; - - parameters.ClientBuilder ??= new Func((address) => - { - var wrapperPrm = new WrapperPrm() - { - Address = address, - Key = parameters.Key!, - Logger = parameters.Logger, - DialTimeout = parameters.NodeDialTimeout, - StreamTimeout = parameters.NodeStreamTimeout, - ErrorThreshold = parameters.ErrorThreshold, - GracefulCloseOnSwitchTimeout = parameters.GracefulCloseOnSwitchTimeout, - Callback = parameters.Callback, - Interceptors = parameters.Interceptors, - GrpcChannelFactory = parameters.GrpcChannelFactory - - }; - - return new ClientWrapper(wrapperPrm, pool); - } - ); - } - - private ClientWrapper Connection() - { - foreach (var pool in InnerPools!) - { - var client = pool.Connection(); - if (client != null) - { - return client; - } - } - - throw new FrostFsException("Cannot find alive client"); - } - - private static async Task InitSessionForDuration(CallContext ctx, ClientWrapper cw, ulong duration, ECDsa key, bool clientCut) - { - var client = cw.Client; - var networkInfo = await client!.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); - - var epoch = networkInfo.Epoch; - - ulong exp = ulong.MaxValue - epoch < duration - ? ulong.MaxValue - : epoch + duration; - - var prmSessionCreate = new PrmSessionCreate(exp); - - return await client.CreateSessionAsync(prmSessionCreate, ctx).ConfigureAwait(false); - } - - internal static string FormCacheKey(string address, string key) - { - return $"{address}{key}"; - } - - public void Close() - { - CancellationTokenSource.Cancel(); - - //if (InnerPools != null) - //{ - // // close all clients - // foreach (var innerPool in InnerPools) - // foreach (var client in innerPool.Clients) - // if (client.IsDialed()) - // client.Client?.Close(); - //} - } - - // startRebalance runs loop to monitor connection healthy status. - internal void StartRebalance(CallContext ctx) - { - var buffers = new double[RebalanceParams.NodesParams.Length][]; - - for (int i = 0; i < RebalanceParams.NodesParams.Length; i++) - { - var parameters = RebalanceParams.NodesParams[i]; - buffers[i] = new double[parameters.Weights.Count]; - - Task.Run(async () => - { - await Task.Delay((int)RebalanceParams.ClientRebalanceInterval).ConfigureAwait(false); - UpdateNodesHealth(ctx, buffers); - }); - } - } - - private void UpdateNodesHealth(CallContext ctx, double[][] buffers) - { - var tasks = new Task[InnerPools!.Length]; - - for (int i = 0; i < InnerPools.Length; i++) - { - var bufferWeights = buffers[i]; - - tasks[i] = Task.Run(() => UpdateInnerNodesHealth(ctx, i, bufferWeights)); - } - - Task.WaitAll(tasks); - } - - private async ValueTask UpdateInnerNodesHealth(CallContext ctx, int poolIndex, double[] bufferWeights) - { - if (poolIndex > InnerPools!.Length - 1) - { - return; - } - - var pool = InnerPools[poolIndex]; - - var options = RebalanceParams; - - int healthyChanged = 0; - - var tasks = new Task[pool.Clients.Length]; - - for (int j = 0; j < pool.Clients.Length; j++) - { - var client = pool.Clients[j]; - var healthy = false; - string? error = null; - var changed = false; - - try - { - // check timeout settings - changed = await client!.RestartIfUnhealthy(ctx).ConfigureAwait(false); - healthy = true; - bufferWeights[j] = options.NodesParams[poolIndex].Weights[j]; - } - // TODO: specify - catch (FrostFsException e) - { - error = e.Message; - bufferWeights[j] = 0; - - SessionCache.DeleteByPrefix(client.Address); - } - - if (changed) - { - if (!string.IsNullOrEmpty(error)) - { - if (logger != null) - { - FrostFsMessages.HealthChanged(logger, client.Address, healthy, error!); - } - - Interlocked.Exchange(ref healthyChanged, 1); - } - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - - if (Interlocked.CompareExchange(ref healthyChanged, -1, -1) == 1) - { - var probabilities = AdjustWeights(bufferWeights); - - lock (_lock) - { - pool.Sampler = new Sampler(probabilities); - } - } - } - } - - - // TODO: remove - private bool CheckSessionTokenErr(Exception error, string address) - { - if (error == null) - { - return false; - } - - if (error is SessionNotFoundException || error is SessionExpiredException) - { - this.SessionCache.DeleteByPrefix(address); - return true; - } - - return false; - } - - public Statistic Statistic() - { - if (InnerPools == null) - { - throw new FrostFsInvalidObjectException(nameof(Pool)); - } - - var statistics = new Statistic(); - - foreach (var inner in InnerPools) - { - int valueIndex = 0; - var nodes = new string[inner.Clients.Length]; - - lock (_lock) - { - foreach (var client in inner.Clients) - { - if (client.IsHealthy()) - { - nodes[valueIndex] = client.Address; - } - - var node = new NodeStatistic - { - Address = client.Address, - Methods = client.MethodsStatus(), - OverallErrors = client.GetOverallErrorRate(), - CurrentErrors = client.GetCurrentErrorRate() - }; - - statistics.Nodes.Add(node); - - valueIndex++; - - statistics.OverallErrors += node.OverallErrors; - } - - if (statistics.CurrentNodes == null || statistics.CurrentNodes.Length == 0) - { - statistics.CurrentNodes = nodes; - } - } - } - - return statistics; - } - - public async Task GetNetmapSnapshotAsync(CallContext ctx) - { - var client = Connection(); - - return await client.Client!.GetNetmapSnapshotAsync(ctx).ConfigureAwait(false); - } - - public async Task GetNodeInfoAsync(CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetNodeInfoAsync(ctx).ConfigureAwait(false); - } - - public async Task GetNetworkSettingsAsync(CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); - } - - public async Task CreateSessionAsync(PrmSessionCreate args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.CreateSessionAsync(args, ctx).ConfigureAwait(false); - } - - public async Task> AddChainAsync(PrmApeChainAdd args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.AddChainAsync(args, ctx).ConfigureAwait(false); - } - - public async Task RemoveChainAsync(PrmApeChainRemove args, CallContext ctx) - { - var client = Connection(); - await client.Client!.RemoveChainAsync(args, ctx).ConfigureAwait(false); - } - - public async Task ListChainAsync(PrmApeChainList args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.ListChainAsync(args, ctx).ConfigureAwait(false); - } - - public async Task GetContainerAsync(PrmContainerGet args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetContainerAsync(args, ctx).ConfigureAwait(false); - } - - public IAsyncEnumerable ListContainersAsync(PrmContainerGetAll args, CallContext ctx) - { - var client = Connection(); - return client.Client!.ListContainersAsync(args, ctx); - } - - [Obsolete("Use PutContainerAsync method")] - public async Task CreateContainerAsync(PrmContainerCreate args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PutContainerAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PutContainerAsync(PrmContainerCreate args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PutContainerAsync(args, ctx).ConfigureAwait(false); - } - - public async Task DeleteContainerAsync(PrmContainerDelete args, CallContext ctx) - { - var client = Connection(); - await client.Client!.DeleteContainerAsync(args, ctx).ConfigureAwait(false); - } - - public async Task GetObjectHeadAsync(PrmObjectHeadGet args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetObjectHeadAsync(args, ctx).ConfigureAwait(false); - } - - public async Task GetObjectAsync(PrmObjectGet args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PutObjectAsync(PrmObjectPut args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PutObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PutClientCutObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PutSingleObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PatchObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task GetRangeAsync(PrmRangeGet args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetRangeAsync(args, ctx).ConfigureAwait(false); - } - - public async Task[]> GetRangeHashAsync(PrmRangeHashGet args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetRangeHashAsync(args, ctx).ConfigureAwait(false); - } - - public async Task PatchAsync(PrmObjectPatch args, CallContext ctx) - { - var client = Connection(); - return await client.Client!.PatchObjectAsync(args, ctx).ConfigureAwait(false); - } - - public async Task DeleteObjectAsync(PrmObjectDelete args, CallContext ctx) - { - var client = Connection(); - await client.Client!.DeleteObjectAsync(args, ctx).ConfigureAwait(false); - } - - public IAsyncEnumerable SearchObjectsAsync(PrmObjectSearch args, CallContext ctx) - { - var client = Connection(); - return client.Client!.SearchObjectsAsync(args, ctx); - } - - public async Task GetBalanceAsync(CallContext ctx) - { - var client = Connection(); - return await client.Client!.GetBalanceAsync(ctx).ConfigureAwait(false); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - Close(); - } - - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(disposing: true); - } -} diff --git a/src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs b/src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs deleted file mode 100644 index 988002c..0000000 --- a/src/FrostFS.SDK.Client/Pool/RebalanceParameters.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FrostFS.SDK.Client; - -public class RebalanceParameters( - NodesParam[] nodesParams, - ulong nodeRequestTimeout, - ulong clientRebalanceInterval, - ulong sessionExpirationDuration) -{ - public NodesParam[] NodesParams { get; set; } = nodesParams; - - public ulong NodeRequestTimeout { get; set; } = nodeRequestTimeout; - - public ulong ClientRebalanceInterval { get; set; } = clientRebalanceInterval; - - public ulong SessionExpirationDuration { get; set; } = sessionExpirationDuration; -} diff --git a/src/FrostFS.SDK.Client/Pool/RequestInfo.cs b/src/FrostFS.SDK.Client/Pool/RequestInfo.cs deleted file mode 100644 index 3b70650..0000000 --- a/src/FrostFS.SDK.Client/Pool/RequestInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace FrostFS.SDK.Client; - -// RequestInfo groups info about pool request. -struct RequestInfo -{ - public string Address { get; set; } - - public MethodIndex MethodIndex { get; set; } - - public TimeSpan Elapsed { get; set; } -} - diff --git a/src/FrostFS.SDK.Client/Pool/Sampler.cs b/src/FrostFS.SDK.Client/Pool/Sampler.cs deleted file mode 100644 index 275f3f0..0000000 --- a/src/FrostFS.SDK.Client/Pool/Sampler.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; - -namespace FrostFS.SDK.Client; - -internal sealed class Sampler -{ - private readonly object _lock = new(); - - private Random random = new(); - - internal double[] Probabilities { get; set; } - internal int[] Alias { get; set; } - - internal Sampler(double[] probabilities) - { - var small = new WorkList(); - var large = new WorkList(); - - var n = probabilities.Length; - - // sampler.randomGenerator = rand.New(source) - Probabilities = new double[n]; - Alias = new int[n]; - - // Compute scaled probabilities. - var p = new double[n]; - - for (int i = 0; i < n; i++) - { - p[i] = probabilities[i] * n; - if (p[i] < 1) - small.Add(i); - else - large.Add(i); - } - - while (small.Length > 0 && large.Length > 0) - { - var l = small.Remove(); - var g = large.Remove(); - - Probabilities[l] = p[l]; - Alias[l] = g; - - p[g] = p[g] + p[l] - 1; - - if (p[g] < 1) - small.Add(g); - else - large.Add(g); - } - - while (large.Length > 0) - { - var g = large.Remove(); - Probabilities[g] = 1; - } - - while (small.Length > 0) - { - var l = small.Remove(); - probabilities[l] = 1; - } - } - - internal int Next() - { - var n = Alias.Length; - - int i; - double f; - lock (_lock) - { - i = random.Next(0, n - 1); - f = random.NextDouble(); - } - - if (f < Probabilities[i]) - { - return i; - } - - return Alias[i]; - } -} diff --git a/src/FrostFS.SDK.Client/Pool/Statistic.cs b/src/FrostFS.SDK.Client/Pool/Statistic.cs deleted file mode 100644 index c4977d5..0000000 --- a/src/FrostFS.SDK.Client/Pool/Statistic.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.ObjectModel; - -namespace FrostFS.SDK.Client; - -public sealed class Statistic -{ - public ulong OverallErrors { get; internal set; } - - public Collection Nodes { get; } = []; - - public string[]? CurrentNodes { get; internal set; } -} diff --git a/src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs b/src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs deleted file mode 100644 index 2156f99..0000000 --- a/src/FrostFS.SDK.Client/Pool/StatusSnapshot.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace FrostFS.SDK.Client; - -public class StatusSnapshot() -{ - public ulong AllTime { get; internal set; } - - public ulong AllRequests { get; internal set; } -} diff --git a/src/FrostFS.SDK.Client/Pool/WorkList.cs b/src/FrostFS.SDK.Client/Pool/WorkList.cs deleted file mode 100644 index 7796e86..0000000 --- a/src/FrostFS.SDK.Client/Pool/WorkList.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace FrostFS.SDK.Client; - -internal sealed class WorkList -{ - private readonly List elements = []; - - internal int Length - { - get { return elements.Count; } - } - - internal void Add(int element) - { - elements.Add(element); - } - - internal int Remove() - { - int last = elements.LastOrDefault(); - elements.RemoveAt(elements.Count - 1); - return last; - } -} diff --git a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs b/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs deleted file mode 100644 index 83ac869..0000000 --- a/src/FrostFS.SDK.Client/Pool/WrapperPrm.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Security.Cryptography; - -using Grpc.Core; -using Grpc.Core.Interceptors; - -using Microsoft.Extensions.Logging; - -namespace FrostFS.SDK.Client; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "")] -public struct WrapperPrm -{ - internal ILogger? Logger { get; set; } - - internal string Address { get; set; } - - internal ECDsa Key { get; set; } - - internal ulong DialTimeout { get; set; } - - internal ulong StreamTimeout { get; set; } - - internal uint ErrorThreshold { get; set; } - - internal Action ResponseInfoCallback { get; set; } - - internal Action PoolRequestInfoCallback { get; set; } - - internal Func GrpcChannelFactory { get; set; } - - internal ulong GracefulCloseOnSwitchTimeout { get; set; } - - internal Action? Callback { get; set; } - - internal Collection? Interceptors { get; set; } -} - diff --git a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs index 8ad35d5..ebd60de 100644 --- a/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ApeManagerServiceProvider.cs @@ -20,8 +20,6 @@ internal sealed class ApeManagerServiceProvider : ContextAccessor { var binary = RuleSerializer.Serialize(args.Chain); - var base64 = Convert.ToBase64String(binary); - AddChainRequest request = new() { Body = new() diff --git a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs index 59783cf..c0f8b1a 100644 --- a/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ContainerServiceProvider.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography; using System.Threading.Tasks; using FrostFS.Container; @@ -83,7 +82,7 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService Body = new PutRequest.Types.Body { Container = grpcContainer, - Signature = ClientContext.Key.ECDsaKey.SignRFC6979(grpcContainer) + Signature = ClientContext.Key.SignRFC6979(grpcContainer) } }; @@ -113,8 +112,8 @@ internal sealed class ContainerServiceProvider(ContainerService.ContainerService { Body = new DeleteRequest.Types.Body { - ContainerId = args.ContainerId.ToMessage(), - Signature = ClientContext.Key.ECDsaKey.SignRFC6979(args.ContainerId.ToMessage().Value) + ContainerId = args.ContainerId.GetContainerID(), + Signature = ClientContext.Key.SignRFC6979(args.ContainerId.GetContainerID().Value) } }; diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index 3ce543d..ea3b727 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -96,7 +96,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Address = new Address { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.GetContainerID(), ObjectId = args.ObjectId.ToMessage() } } @@ -124,7 +124,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Address = new Address { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.GetContainerID(), ObjectId = args.ObjectId.ToMessage() }, Range = new Object.Range @@ -159,7 +159,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Address = new Address { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.GetContainerID(), ObjectId = args.ObjectId.ToMessage() }, Type = ChecksumType.Sha256, @@ -204,7 +204,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Address = new Address { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.GetContainerID(), ObjectId = args.ObjectId.ToMessage() } } @@ -231,7 +231,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Body = new SearchRequest.Types.Body { - ContainerId = args.ContainerId.ToMessage(), + ContainerId = args.ContainerId.GetContainerID(), Version = 1 // TODO: clarify this param } }; @@ -296,18 +296,17 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { var chunkSize = args.MaxChunkLength; Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); - + var call = client.Patch(null, ctx.GetDeadline(), ctx.CancellationToken); byte[]? chunkBuffer = null; try { - // common chunkBuffer = ArrayPool.Shared.Rent(chunkSize); bool isFirstChunk = true; ulong currentPos = args.Range.Offset; - + var address = new Address { ObjectId = args.Address.ObjectId, @@ -327,11 +326,11 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { Body = new() { - Address = address, + Address = address, Patch = new PatchRequest.Types.Body.Types.Patch { Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory(0, bytesCount)), - SourceRange = new Object.Range { Offset = currentPos, Length = (ulong)bytesCount } + SourceRange = new Range { Offset = currentPos, Length = (ulong)bytesCount } } } }; @@ -385,7 +384,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return response.Body.ObjectId.ToModel(); } - + internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { var stream = args.Payload!; @@ -567,7 +566,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl break; sentBytes += bytesCount; - + var chunkRequest = new PutRequest { Body = new PutRequest.Types.Body @@ -622,7 +621,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var initRequest = new PutRequest { Body = new PutRequest.Types.Body - { + { Init = new PutRequest.Types.Body.Types.Init { Header = grpcHeader, diff --git a/src/FrostFS.SDK.Client/Pool/SessionCache.cs b/src/FrostFS.SDK.Client/Services/Shared/SessionCache.cs similarity index 72% rename from src/FrostFS.SDK.Client/Pool/SessionCache.cs rename to src/FrostFS.SDK.Client/Services/Shared/SessionCache.cs index fae3de1..a02436f 100644 --- a/src/FrostFS.SDK.Client/Pool/SessionCache.cs +++ b/src/FrostFS.SDK.Client/Services/Shared/SessionCache.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace FrostFS.SDK.Client; @@ -36,15 +35,4 @@ internal sealed class SessionCache(ulong sessionExpirationDuration) _cache[key] = value; } } - - internal void DeleteByPrefix(string prefix) - { - foreach (var key in _cache.Keys) - { - if (key.StartsWith(prefix, StringComparison.Ordinal)) - { - _cache.TryRemove(key, out var _); - } - } - } } diff --git a/src/FrostFS.SDK.Client/Tools/ClientContext.cs b/src/FrostFS.SDK.Client/Tools/ClientContext.cs index e2522c9..104cb3d 100644 --- a/src/FrostFS.SDK.Client/Tools/ClientContext.cs +++ b/src/FrostFS.SDK.Client/Tools/ClientContext.cs @@ -39,7 +39,7 @@ public class ClientContext(FrostFSClient client, ClientKey key, FrostFsOwner own { if (sessionKey == null && Key != null && Address != null) { - sessionKey = Pool.FormCacheKey(Address, Key.PublicKey); + sessionKey = $"{Address}{Key}"; } return sessionKey; diff --git a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs index ac5d019..c8c5caf 100644 --- a/src/FrostFS.SDK.Client/Tools/ObjectTools.cs +++ b/src/FrostFS.SDK.Client/Tools/ObjectTools.cs @@ -1,6 +1,6 @@ using System; using System.Linq; - +using System.Security.Cryptography; using FrostFS.Object; using FrostFS.Refs; using FrostFS.SDK.Client.Mappers.GRPC; @@ -44,7 +44,11 @@ public static class ObjectTools if (header.Split != null) SetSplitValues(grpcHeader, header.Split, owner, version, key); - return new ObjectID { Value = grpcHeader.Sha256() }.ToModel(); + using var sha256 = SHA256.Create(); + using HashStream stream = new(sha256); + grpcHeader.WriteTo(stream); + + return new FrostFsObjectId(Base58.Encode(stream.Hash())); } internal static Object.Object CreateSingleObject(FrostFsObject @object, ClientContext ctx) @@ -75,7 +79,7 @@ public static class ObjectTools var obj = new Object.Object { Header = grpcHeader, - ObjectId = new ObjectID { Value = grpcHeader.Sha256() }, + ObjectId = new ObjectID { Value = UnsafeByteOperations.UnsafeWrap(grpcHeader.Sha256()) }, Payload = UnsafeByteOperations.UnsafeWrap(@object.SingleObjectPayload) }; @@ -117,9 +121,9 @@ public static class ObjectTools if (split.ParentHeader is not null) { - var grpcParentHeader = CreateHeader(split.ParentHeader, Array.Empty().Sha256(), owner, version); + var grpcParentHeader = CreateHeader(split.ParentHeader, DataHasher.Sha256([]), owner, version); - grpcHeader.Split.Parent = new ObjectID { Value = grpcParentHeader.Sha256() }; + grpcHeader.Split.Parent = new ObjectID { Value = UnsafeByteOperations.UnsafeWrap(grpcParentHeader.Sha256()) }; grpcHeader.Split.ParentHeader = grpcParentHeader; grpcHeader.Split.ParentSignature = new Signature { @@ -147,21 +151,12 @@ public static class ObjectTools return grpcHeader; } - internal static Checksum Sha256Checksum(byte[] data) - { - return new Checksum - { - Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(data.Sha256()) - }; - } - internal static Checksum Sha256Checksum(ReadOnlyMemory data) { return new Checksum { Type = ChecksumType.Sha256, - Sum = ByteString.CopyFrom(data.Sha256()) + Sum = UnsafeByteOperations.UnsafeWrap(DataHasher.Sha256(data)) }; } diff --git a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs index e2aceef..8df1b7c 100644 --- a/src/FrostFS.SDK.Client/Tools/RequestSigner.cs +++ b/src/FrostFS.SDK.Client/Tools/RequestSigner.cs @@ -33,6 +33,7 @@ public static class RequestSigner var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); var privateKey = new ECPrivateKeyParameters(new BigInteger(1, key.PrivateKey()), ecParameters); var signer = new ECDsaSigner(new HMacDsaKCalculator(digest)); + var hash = new byte[digest.GetDigestSize()]; digest.BlockUpdate(data, 0, data.Length); @@ -54,21 +55,21 @@ public static class RequestSigner return ByteString.CopyFrom(signature); } - internal static SignatureRFC6979 SignRFC6979(this ECDsa key, IMessage message) + internal static SignatureRFC6979 SignRFC6979(this ClientKey key, IMessage message) { return new SignatureRFC6979 { - Key = ByteString.CopyFrom(key.PublicKey()), - Sign = key.SignRFC6979(message.ToByteArray()), + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignRFC6979(message.ToByteArray()), }; } - internal static SignatureRFC6979 SignRFC6979(this ECDsa key, ByteString data) + internal static SignatureRFC6979 SignRFC6979(this ClientKey key, ByteString data) { return new SignatureRFC6979 { - Key = ByteString.CopyFrom(key.PublicKey()), - Sign = key.SignRFC6979(data.ToByteArray()), + Key = key.PublicKeyProto, + Sign = key.ECDsaKey.SignRFC6979(data.ToByteArray()), }; } @@ -82,11 +83,11 @@ public static class RequestSigner Span result = stackalloc byte[65]; result[0] = 0x04; - key.SignHash(data.Sha512()).AsSpan().CopyTo(result.Slice(1)); + key.SignHash(DataHasher.Sha512(data)).AsSpan().CopyTo(result.Slice(1)); return ByteString.CopyFrom(result); } - + public static ByteString SignDataByHash(this ECDsa key, byte[] hash) { if (key is null) @@ -112,8 +113,9 @@ public static class RequestSigner Sign = key.ECDsaKey.SignData(ReadOnlyMemory.Empty), }; } - - using HashStream stream = new(); + + using var sha512 = SHA512.Create(); + using HashStream stream = new(sha512); data.WriteTo(stream); var sig = new Signature diff --git a/src/FrostFS.SDK.Client/Tools/Verifier.cs b/src/FrostFS.SDK.Client/Tools/Verifier.cs index c110431..90eb3ce 100644 --- a/src/FrostFS.SDK.Client/Tools/Verifier.cs +++ b/src/FrostFS.SDK.Client/Tools/Verifier.cs @@ -9,61 +9,13 @@ using FrostFS.Session; using Google.Protobuf; -using Org.BouncyCastle.Asn1.Sec; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.Math; - namespace FrostFS.SDK.Client; public static class Verifier { public const int RFC6979SignatureSize = 64; - private static BigInteger[] DecodeSignature(byte[] sig) - { - if (sig.Length != RFC6979SignatureSize) - throw new FormatException($"Wrong signature size, expect={RFC6979SignatureSize}, actual={sig.Length}"); - - var rs = new BigInteger[2]; - rs[0] = new BigInteger(1, sig.AsSpan(0, 32).ToArray()); - rs[1] = new BigInteger(1, sig.AsSpan(32).ToArray()); - - return rs; - } - - public static bool VerifyRFC6979(this byte[] publicKey, byte[] data, byte[] sig) - { - if (publicKey is null || data is null || sig is null) - return false; - - var rs = DecodeSignature(sig); - var digest = new Sha256Digest(); - var signer = new ECDsaSigner(new HMacDsaKCalculator(digest)); - var secp256R1 = SecNamedCurves.GetByName("secp256r1"); - var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); - var bcPublicKey = new ECPublicKeyParameters(secp256R1.Curve.DecodePoint(publicKey), ecParameters); - var hash = new byte[digest.GetDigestSize()]; - - digest.BlockUpdate(data, 0, data.Length); - digest.DoFinal(hash, 0); - signer.Init(false, bcPublicKey); - - return signer.VerifySignature(hash, rs[0], rs[1]); - } - - public static bool VerifyRFC6979(this SignatureRFC6979 signature, IMessage message) - { - if (signature is null) - { - throw new ArgumentNullException(nameof(signature)); - } - - return signature.Key.ToByteArray().VerifyRFC6979(message.ToByteArray(), signature.Sign.ToByteArray()); - } - - public static bool VerifyData(this ECDsa key, ReadOnlyMemory data, byte[] sig) + public static bool VerifyData(this ECDsa key, IMessage data, ByteString sig) { if (key is null) throw new ArgumentNullException(nameof(key)); @@ -71,7 +23,18 @@ public static class Verifier if (sig is null) throw new ArgumentNullException(nameof(sig)); - return key.VerifyHash(data.Sha512(), sig.AsSpan(1).ToArray()); + var signature = sig.Span.Slice(1).ToArray(); + using var sha = SHA512.Create(); + + if (data is null) + { + return key.VerifyHash(DataHasher.Sha512(new Span([])), signature); + } + + using var stream = new HashStream(sha); + data.WriteTo(stream); + + return key.VerifyHash(stream.Hash(), signature); } public static bool VerifyMessagePart(this Signature sig, IMessage data) @@ -80,9 +43,8 @@ public static class Verifier return false; using var key = sig.Key.ToByteArray().LoadPublicKey(); - var data2Verify = data is null ? [] : data.ToByteArray(); - return key.VerifyData(data2Verify, sig.Sign.ToByteArray()); + return key.VerifyData(data, sig.Sign); } internal static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification) diff --git a/src/FrostFS.SDK.Cryptography/ArrayHelper.cs b/src/FrostFS.SDK.Cryptography/ArrayHelper.cs index 00c356d..c88134d 100644 --- a/src/FrostFS.SDK.Cryptography/ArrayHelper.cs +++ b/src/FrostFS.SDK.Cryptography/ArrayHelper.cs @@ -21,4 +21,18 @@ internal static class ArrayHelper return dst; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetRevertedArray(ReadOnlySpan source, byte[] data) + { + if (source.Length != 0) + { + int i = 0; + int j = source.Length - 1; + while (i < source.Length) + { + data[i++] = source[j--]; + } + } + } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index c03d604..9f33f25 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: AssemblyCompany("FrostFS.SDK.Cryptography")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyFileVersion("1.0.4.0")] [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] -[assembly: AssemblyVersion("1.0.3.0")] +[assembly: AssemblyVersion("1.0.4.0")] diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 636eb0a..0026bc3 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -19,19 +19,20 @@ public static class Base58 if (buffer.Length < 4) throw new FormatException(); - var check = buffer.AsSpan(0, buffer.Length - 4).ToArray(); - byte[] checksum = check.Sha256().Sha256(); + var check = buffer.AsSpan(0, buffer.Length - 4); + byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(check).AsSpan()); if (!buffer.AsSpan(buffer.Length - 4).SequenceEqual(checksum.AsSpan(0, 4))) throw new FormatException(); + var result = check.ToArray(); Array.Clear(buffer, 0, buffer.Length); - return check; + return result; } - public static string Base58CheckEncode(this ReadOnlySpan data) + public static string Base58CheckEncode(this Span data) { - byte[] checksum = data.ToArray().Sha256().Sha256(); + byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(data).AsSpan()); ; Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); @@ -59,10 +60,9 @@ public static class Base58 } int leadingZeroCount = input.TakeWhile(c => c == Alphabet[0]).Count(); - var leadingZeros = new byte[leadingZeroCount]; if (bi.IsZero) - return leadingZeros; + return new byte[leadingZeroCount]; var bytesBigEndian = bi.ToByteArray().Reverse().ToArray(); @@ -73,12 +73,26 @@ public static class Base58 var bytesWithoutLeadingZeros = bytesBigEndian.Skip(firstNonZeroIndex).ToArray(); - return ArrayHelper.Concat(leadingZeros, bytesWithoutLeadingZeros); + var result = new byte[leadingZeroCount + bytesBigEndian.Length - firstNonZeroIndex]; + + int p = 0; + while (p < leadingZeroCount) + result[p++] = 0; + + for (int j = firstNonZeroIndex; j < bytesBigEndian.Length; j++) + result[p++] = bytesBigEndian[j]; + + return result; } public static string Encode(ReadOnlySpan input) { - var data = input.ToArray().Reverse().Concat(new byte[] { 0 }).ToArray(); + var data = new byte[input.Length + 1]; + + ArrayHelper.GetRevertedArray(input, data); + + data[input.Length] = 0; + BigInteger value = new(data); // Encode BigInteger to Base58 string diff --git a/src/FrostFS.SDK.Cryptography/DataHasher.cs b/src/FrostFS.SDK.Cryptography/DataHasher.cs new file mode 100644 index 0000000..fceeeaa --- /dev/null +++ b/src/FrostFS.SDK.Cryptography/DataHasher.cs @@ -0,0 +1,121 @@ +using System; +using System.Buffers; +using System.Security.Cryptography; + +namespace FrostFS.SDK.Cryptography; + +public static class DataHasher +{ + private const int LargeBlockSize = 1024 * 1024; + private const int SmallBlockSize = 256; + + public static byte[] Hash(this ReadOnlyMemory bytes, HashAlgorithm algorithm) + { + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } + + if (bytes.Length == 0) + { + return algorithm.ComputeHash([]); + } + + int rest, pos = 0; + + var blockSize = bytes.Length <= SmallBlockSize ? SmallBlockSize : LargeBlockSize; + + byte[] buffer = ArrayPool.Shared.Rent(blockSize); + + try + { + while ((rest = bytes.Length - pos) > 0) + { + var size = Math.Min(rest, blockSize); + + bytes.Slice(pos, size).CopyTo(buffer); + + algorithm.TransformBlock(buffer, 0, size, buffer, 0); + + pos += size; + } + + algorithm.TransformFinalBlock([], 0, 0); + return algorithm.Hash; + } + finally + { + if (buffer != null) + { + ArrayPool.Shared.Return(buffer); + } + } + } + + public static byte[] Hash(this ReadOnlySpan bytes, HashAlgorithm algorithm) + { + if (algorithm is null) + { + throw new ArgumentNullException(nameof(algorithm)); + } + + if (bytes.Length == 0) + { + return algorithm.ComputeHash([]); + } + + int rest, pos = 0; + + var blockSize = bytes.Length <= SmallBlockSize ? SmallBlockSize : LargeBlockSize; + + byte[] buffer = ArrayPool.Shared.Rent(blockSize); + + try + { + while ((rest = bytes.Length - pos) > 0) + { + var size = Math.Min(rest, blockSize); + + bytes.Slice(pos, size).CopyTo(buffer); + + algorithm.TransformBlock(buffer, 0, size, buffer, 0); + + pos += size; + } + + algorithm.TransformFinalBlock([], 0, 0); + return algorithm.Hash; + } + finally + { + if (buffer != null) + { + ArrayPool.Shared.Return(buffer); + } + } + } + + public static byte[] Sha256(ReadOnlyMemory value) + { + using SHA256 sha = SHA256.Create(); + return Hash(value, sha); + } + + public static byte[] Sha256(ReadOnlySpan value) + { + using SHA256 sha = SHA256.Create(); + return Hash(value, sha); + } + + public static byte[] Sha512(ReadOnlyMemory value) + { + using SHA512 sha = SHA512.Create(); + return Hash(value, sha); + } + + public static byte[] Sha512(ReadOnlySpan value) + { + using SHA512 sha = SHA512.Create(); + return Hash(value, sha); + } +} diff --git a/src/FrostFS.SDK.Cryptography/Extentions.cs b/src/FrostFS.SDK.Cryptography/Extentions.cs index 40ad6f0..5f4fdd9 100644 --- a/src/FrostFS.SDK.Cryptography/Extentions.cs +++ b/src/FrostFS.SDK.Cryptography/Extentions.cs @@ -1,20 +1,9 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Threading; -using CommunityToolkit.HighPerformance; using Org.BouncyCastle.Crypto.Digests; namespace FrostFS.SDK.Cryptography; public static class Extentions { - private static readonly SHA256 _sha256 = SHA256.Create(); - private static SpinLock _spinlockSha256; - - private static readonly SHA512 _sha512 = SHA512.Create(); - private static SpinLock _spinlockSha512; - internal static byte[] RIPEMD160(this byte[] value) { var hash = new byte[20]; @@ -24,72 +13,4 @@ public static class Extentions digest.DoFinal(hash, 0); return hash; } - - public static byte[] Sha256(this byte[] value) - { - bool lockTaken = false; - try - { - _spinlockSha256.Enter(ref lockTaken); - - return _sha256.ComputeHash(value); - } - finally - { - if (lockTaken) - { - _spinlockSha256.Exit(false); - } - } - } - - public static byte[] Sha256(this ReadOnlyMemory value) - { - bool lockTaken = false; - try - { - _spinlockSha256.Enter(ref lockTaken); - - return _sha256.ComputeHash(value.AsStream()); - } - finally - { - if (lockTaken) - { - _spinlockSha256.Exit(false); - } - } - } - - public static byte[] Sha512(this ReadOnlyMemory value) - { - bool lockTaken = false; - try - { - _spinlockSha512.Enter(ref lockTaken); - - return _sha512.ComputeHash(value.AsStream()); - } - finally - { - if (lockTaken) - _spinlockSha512.Exit(false); - } - } - - public static byte[] Sha512(this Stream stream) - { - bool lockTaken = false; - try - { - _spinlockSha512.Enter(ref lockTaken); - - return _sha512.ComputeHash(stream); - } - finally - { - if (lockTaken) - _spinlockSha512.Exit(false); - } - } } diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index e4c7824..9dfd370 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -30,7 +30,6 @@ - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/FrostFS.SDK.Cryptography/HashStream.cs b/src/FrostFS.SDK.Cryptography/HashStream.cs index d2ede1a..2893f26 100644 --- a/src/FrostFS.SDK.Cryptography/HashStream.cs +++ b/src/FrostFS.SDK.Cryptography/HashStream.cs @@ -3,11 +3,11 @@ using System.Security.Cryptography; namespace FrostFS.SDK.Cryptography; -public sealed class HashStream() : Stream +public sealed class HashStream(HashAlgorithm algorithm) : Stream { private long position; - private readonly SHA512 _hash = SHA512.Create(); + private readonly HashAlgorithm _hash = algorithm; public override bool CanRead => false; diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index 072f155..5f382fa 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -16,7 +16,7 @@ public static class KeyExtension private const int UncompressedPublicKeyLength = 65; private static readonly uint CheckSigDescriptor = - BinaryPrimitives.ReadUInt32LittleEndian(Encoding.ASCII.GetBytes("System.Crypto.CheckSig").Sha256()); + BinaryPrimitives.ReadUInt32LittleEndian(DataHasher.Sha256(Encoding.ASCII.GetBytes("System.Crypto.CheckSig").AsSpan())); public static byte[] Compress(this byte[] publicKey) { @@ -60,10 +60,15 @@ public static class KeyExtension $"expected length={CompressedPublicKeyLength}, actual={publicKey.Length}" ); - var script = new byte[] { 0x0c, CompressedPublicKeyLength }; //PUSHDATA1 33 - script = ArrayHelper.Concat(script, publicKey); - script = ArrayHelper.Concat(script, [0x41]); //SYSCALL - script = ArrayHelper.Concat(script, BitConverter.GetBytes(CheckSigDescriptor)); //Neo_Crypto_CheckSig + var signDescriptor = BitConverter.GetBytes(CheckSigDescriptor); + + var script = new byte[3 + publicKey.Length + signDescriptor.Length]; + + script[0] = 0x0c; + script[1] = CompressedPublicKeyLength; //PUSHDATA1 33 + Buffer.BlockCopy(publicKey, 0, script, 2, publicKey.Length); + script[publicKey.Length + 2] = 0x41; //SYSCALL + Buffer.BlockCopy(signDescriptor, 0, script, publicKey.Length + 3, signDescriptor.Length); return script; } @@ -74,7 +79,7 @@ public static class KeyExtension throw new ArgumentNullException(nameof(publicKey)); var script = publicKey.CreateSignatureRedeemScript(); - return script.Sha256().RIPEMD160(); + return DataHasher.Sha256(script.AsSpan()).RIPEMD160(); } private static string ToAddress(this byte[] scriptHash, byte version) @@ -102,11 +107,6 @@ public static class KeyExtension return privateKey; } - public static string Address(this ECDsa key) - { - return key.PublicKey().PublicKeyToAddress(); - } - public static string PublicKeyToAddress(this byte[] publicKey) { if (publicKey == null) @@ -132,10 +132,12 @@ public static class KeyExtension var pos = 33 - param.Q.X.Length; param.Q.X.CopyTo(pubkey, pos); - if (new BigInteger(param.Q.Y.Reverse().Concat(new byte[] { 0x0 }).ToArray()).IsEven) - pubkey[0] = 0x2; - else - pubkey[0] = 0x3; + + var y = new byte[33]; + ArrayHelper.GetRevertedArray(param.Q.Y, y); + y[32] = 0; + + pubkey[0] = new BigInteger(y).IsEven ? (byte)0x2 : (byte)0x3; return pubkey; } @@ -152,7 +154,9 @@ public static class KeyExtension { var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var publicKey = secp256R1.G.Multiply(new Org.BouncyCastle.Math.BigInteger(1, privateKey)) - .GetEncoded(false).Skip(1).ToArray(); + .GetEncoded(false) + .Skip(1) + .ToArray(); var key = ECDsa.Create(new ECParameters { diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index 11ace79..c3c813c 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: AssemblyCompany("FrostFS.SDK.Protos")] -[assembly: AssemblyFileVersion("1.0.2.0")] +[assembly: AssemblyFileVersion("1.0.4.0")] [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] -[assembly: AssemblyVersion("1.0.3.0")] +[assembly: AssemblyVersion("1.0.4.0")] diff --git a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs index 0a3aec6..58a9d40 100644 --- a/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/AsyncStreamReaderMock.cs @@ -3,7 +3,6 @@ using System.Security.Cryptography; using FrostFS.Object; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using FrostFS.Session; using Google.Protobuf; @@ -43,7 +42,7 @@ public class AsyncStreamReaderMock(string key, FrostFsObjectHeader objectHeader) Signature = new Refs.Signature { Key = Key.PublicKeyProto, - Sign = Key.ECDsaKey. SignData(header.ToByteArray()), + Sign = Key.ECDsaKey.SignData(header.ToByteArray()), } } }, diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs index e6f25c2..eafdda5 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/ContainerServiceBase.cs @@ -1,5 +1,3 @@ -using System.Security.Cryptography; - using FrostFS.Container; using FrostFS.Object; using FrostFS.SDK.Client; diff --git a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs index 0ad6ab0..7491950 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ContainerServiceMocks/GetContainerMock.cs @@ -23,13 +23,16 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) var grpcVersion = Version.ToMessage(); + Span ContainerGuidSpan = stackalloc byte[16]; + ContainerGuid.ToBytes(ContainerGuidSpan); + PutResponse putResponse = new() { Body = new PutResponse.Types.Body { ContainerId = new ContainerID { - Value = ByteString.CopyFrom(ContainerGuid.ToBytes()) + Value = ByteString.CopyFrom(ContainerGuidSpan) } }, MetaHeader = new ResponseMetaHeader @@ -69,7 +72,7 @@ public class ContainerMocker(string key) : ContainerServiceBase(key) Container = new Container.Container { Version = grpcVersion, - Nonce = ByteString.CopyFrom(ContainerGuid.ToBytes()), + Nonce = ByteString.CopyFrom(ContainerGuidSpan), PlacementPolicy = PlacementPolicy.GetPolicy() } }, diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index 49a0f6d..f33b9a1 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -4,7 +4,6 @@ using System.Security.Cryptography; using FrostFS.Object; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; using Google.Protobuf; diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs index 37a8c13..04a8b2c 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs @@ -40,9 +40,9 @@ public class ContainerTests : SmokeTestsBase { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); await Cleanup(client); - + client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - + FrostFsContainerId containerId = await CreateContainer(client, ctx: default, token: null, @@ -50,22 +50,25 @@ public class ContainerTests : SmokeTestsBase backupFactor: 1, selectors: [], filter: [], - containerAttributes: [new ("testKey1", "testValue1")], + containerAttributes: [new("testKey1", "testValue1")], new FrostFsReplica(3)); Assert.NotNull(containerId); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); - + Assert.NotNull(container); Assert.NotNull(container.Attributes); - Assert.Equal(2, container.Attributes.Count); Assert.Equal("testKey1", container.Attributes[0].Key); Assert.Equal("testValue1", container.Attributes[0].Value); - Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[1].Key); - Assert.Equal("true", container.Attributes[1].Value); - + + //Assert.Equal("true", container.Attributes[1].Value); + + // for cluster + //Assert.Equal(2, container.Attributes.Count); + //Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[1].Key); + Assert.True(container.PlacementPolicy.HasValue); Assert.Equal(1u, container.PlacementPolicy.Value.BackupFactor); @@ -82,7 +85,7 @@ public class ContainerTests : SmokeTestsBase Assert.Equal(OwnerId!.ToString(), container.Owner!.Value); Assert.NotNull(container.Version); - + Assert.Equal(Version!.Major, container.Version.Major); Assert.Equal(Version.Minor, container.Version.Minor); } @@ -101,8 +104,8 @@ public class ContainerTests : SmokeTestsBase new ("filter2", "filterKey2", 2, "testValue2", [new ("subFilter2", "subFilterKey2", 3, "testValue3",[])]) ]; - Collection selectors = [ - new ("selector1") { + Collection selectors = [ + new ("selector1") { Count = 1, Clause = 1, Attribute = "attribute1", @@ -129,19 +132,19 @@ public class ContainerTests : SmokeTestsBase Assert.NotNull(containerId); var container = await client.GetContainerAsync(new PrmContainerGet(containerId), default); - + Assert.NotNull(container); Assert.NotNull(container.Attributes); - Assert.Single(container.Attributes); - Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[0].Key); - Assert.Equal("true", container.Attributes[0].Value); + //Assert.Single(container.Attributes); + //Assert.Equal("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", container.Attributes[0].Key); + //Assert.Equal("true", container.Attributes[0].Value); Assert.True(container.PlacementPolicy.HasValue); Assert.Equal(2u, container.PlacementPolicy.Value.BackupFactor); Assert.False(container.PlacementPolicy.Value.Unique); - + Assert.NotEmpty(container.PlacementPolicy.Value.Selectors); Assert.Equal(2, container.PlacementPolicy.Value.Selectors.Count); @@ -196,7 +199,7 @@ public class ContainerTests : SmokeTestsBase Assert.Equal(OwnerId!.ToString(), container.Owner!.Value); Assert.NotNull(container.Version); - + Assert.Equal(Version!.Major, container.Version.Major); Assert.Equal(Version.Minor, container.Version.Minor); } @@ -233,12 +236,12 @@ public class ContainerTests : SmokeTestsBase }); } - #pragma warning disable xUnit1031 // Timeout is used +#pragma warning disable xUnit1031 // Timeout is used if (!Task.WaitAll(tasks, 20000)) { Assert.Fail("cannot create containers"); } - #pragma warning restore xUnit1031 +#pragma warning restore xUnit1031 var containers = client.ListContainersAsync(new PrmContainerGetAll(), default); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs index f1fce3c..a3d25af 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs @@ -32,8 +32,10 @@ public class SmokeClientTests : SmokeTestsBase Assert.Equal(13, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); - Assert.Equal(2, result.Addresses.Count); - Assert.Equal(11, result.Attributes.Count); + Assert.Single(result.Addresses); + Assert.Equal(9, result.Attributes.Count); + //Assert.Equal(2, result.Addresses.Count); + //Assert.Equal(11, result.Attributes.Count); } [Fact] @@ -65,13 +67,13 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(result.HomomorphicHashingDisabled); Assert.True(result.MaintenanceModeAllowed); Assert.True(0u < result.MagicNumber); - + Assert.Equal(0u, result.AuditFee); Assert.Equal(0u, result.BasicIncomeRate); Assert.Equal(0u, result.ContainerAliasFee); Assert.Equal(0u, result.ContainerFee); Assert.Equal(75u, result.EpochDuration); - Assert.Equal(10_000_000_000u, result.InnerRingCandidateFee); + Assert.Equal(10_000_000_000u, result.InnerRingCandidateFee); Assert.Equal(12u, result.MaxECDataCount); Assert.Equal(4u, result.MaxECParityCount); Assert.Equal(5242880u, result.MaxObjectSize); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs index 03a9169..a0f501f 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -58,7 +58,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase int[] objectSizes = [1, 257, 5 * 1024 * 1024, 20 * 1024 * 1024]; string[] objectTypes = [clientCut, serverCut, singleObject]; - + foreach (var objectSize in objectSizes) { _testOutputHelper.WriteLine($"test set for object size {objectSize}"); @@ -157,7 +157,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase Assert.NotNull(x); Assert.True(x.Length > 0); - // Assert.True(expectedHash.SequenceEqual(h.ToArray())); + // Assert.True(expectedHash.SequenceEqual(h.ToArray())); } } @@ -201,7 +201,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase private static async Task ValidatePatch(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) { - if (bytes.Length < 1024 + 64 || bytes.Length > 5900) + if (bytes.Length < 1024 + 64 || bytes.Length > 5900) return; var patch = new byte[1024]; diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs deleted file mode 100644 index 831f37e..0000000 --- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultiThreadTestsBase.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Security.Cryptography; - -using FrostFS.SDK.Client; -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK.Tests.Smoke; - -public abstract class MultiThreadTestsBase -{ - private TestNodeInfo[] nodes; - - protected CallContext? Ctx { get; } - - protected MultiThreadTestsBase() - { - nodes = new TestNodeInfo[4]; - - nodes[0] = new(new Uri(""), ""); - nodes[1] = new(new Uri(""), ""); - nodes[2] = new(new Uri(""), ""); - nodes[3] = new(new Uri(""), ""); - } -} - -public class TestNodeInfo -{ - internal Uri Uri; - - protected ECDsa? Key { get; } - - protected FrostFsOwner? OwnerId { get; } - - protected FrostFsVersion? Version { get; } - - public TestNodeInfo(Uri uri, string keyString) - { - Uri = uri; - Key = keyString.LoadWif(); - OwnerId = FrostFsOwner.FromKey(Key); - Version = new FrostFsVersion(2, 13); - } -} diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs deleted file mode 100644 index 194ef98..0000000 --- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadPoolSmokeTests.cs +++ /dev/null @@ -1,544 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; - -using FrostFS.SDK.Client; -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK.Tests.Smoke; - -[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class MultithreadPoolSmokeTests : SmokeTestsBase -{ - private InitParameters GetDefaultParams() - { - return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) - { - Key = keyString.LoadWif(), - NodeParams = [new(1, url, 100.0f)], - ClientBuilder = null, - GracefulCloseOnSwitchTimeout = 30_000_000, - Logger = null - }; - } - - [Fact] - public async void NetworkMapTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)); - - Assert.Null(error); - - var result = await pool.GetNetmapSnapshotAsync(default); - - Assert.True(result.Epoch > 0); - Assert.Single(result.NodeInfoCollection); - - var item = result.NodeInfoCollection[0]; - Assert.Equal(2, item.Version.Major); - Assert.Equal(13, item.Version.Minor); - Assert.Equal(NodeState.Online, item.State); - Assert.True(item.PublicKey.Length > 0); - Assert.Single(item.Addresses); - Assert.Equal(9, item.Attributes.Count); - } - - [Fact] - public async void NodeInfoTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - 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); - } - - [Fact] - public async void NodeInfoStatisticsTwoNodesTest() - { - var callbackText = string.Empty; - - var options = new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) - { - Key = keyString.LoadWif(), - NodeParams = [ - new(1, url, 100.0f), - new(2, url.Replace('0', '1'), 100.0f) - ], - ClientBuilder = null, - GracefulCloseOnSwitchTimeout = 30_000_000, - Logger = null, - Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" - }; - - var pool = new Pool(options); - - var ctx = new CallContext(TimeSpan.Zero); - - var error = await pool.Dial(ctx).ConfigureAwait(true); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - var statistics = pool.Statistic(); - - Assert.False(string.IsNullOrEmpty(callbackText)); - Assert.Contains(" took ", callbackText, StringComparison.Ordinal); - } - - [Fact] - public async void NodeInfoStatisticsTest() - { - var options = GetDefaultParams(); - - var callbackText = string.Empty; - options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - - var pool = new Pool(options); - - var ctx = new CallContext(TimeSpan.Zero); - - var error = await pool.Dial(ctx).ConfigureAwait(true); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - Assert.False(string.IsNullOrEmpty(callbackText)); - Assert.Contains(" took ", callbackText, StringComparison.Ordinal); - } - - [Fact] - public async void GetSessionTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - var prm = new PrmSessionCreate(100); - - var token = await pool.CreateSessionAsync(prm, default).ConfigureAwait(true); - - var ownerHash = Base58.Decode(OwnerId!.Value); - - Assert.NotNull(token); - Assert.NotEqual(Guid.Empty, token.Id); - Assert.Equal(33, token.SessionKey.Length); - } - - [Fact] - public async void CreateObjectWithSessionToken() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["key1", "value1"]); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - var bytes = GetRandomBytes(1024); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - } - - [Fact] - public async void FilterTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - 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())); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId, false), default); - - var ecdsaKey = keyString.LoadWif(); - - var networkInfo = await pool.GetNetmapSnapshotAsync(default); - - await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); - - await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - - await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); - - await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); - - await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); - - await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - - await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - - await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); - - var checkSum = CheckSum.CreateCheckSum(bytes); - - await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); - - await CheckFilter(pool, containerId, new FilterByPhysicallyStored()); - } - - private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter) - { - var resultObjectsCount = 0; - - PrmObjectSearch searchParam = new(containerId, null, [], filter); - - await foreach (var objId in pool.SearchObjectsAsync(searchParam, default)) - { - resultObjectsCount++; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); - } - - Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioTest(int objectSize) - { - bool callbackInvoked = false; - - var options = GetDefaultParams(); - - options.Callback = new((cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await pool.PutContainerAsync(createContainerParam, default); - - var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - Assert.True(callbackInvoked); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes!); - Assert.Equal("fileName", objHeader.Attributes!.First().Key); - Assert.Equal("test", objHeader.Attributes!.First().Value); - } - - Assert.True(hasObject); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - - await foreach (var _ in pool.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioWithSessionTest(int objectSize) - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - await Cleanup(pool); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams); - - var container = await pool.PutContainerAsync(createContainerParam, ctx); - - var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); - Assert.NotNull(containerInfo); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: container, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default)) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - - await foreach (var _ in pool.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB - [InlineData(64 * 1024 * 1024 - 1)] - [InlineData(64 * 1024 * 1024 + 1)] - [InlineData(2 * 64 * 1024 * 1024 + 256)] - [InlineData(200)] - public async void ClientCutScenarioTest(int objectSize) - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - var ctx = new CallContext(TimeSpan.FromSeconds(10)); - - // ctx.Interceptors.Add(new CallbackInterceptor()); - - var container = await pool.GetContainerAsync(new PrmContainerGet(containerId), ctx); - - Assert.NotNull(container); - - byte[] bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectClientCutPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes)); - - var objectId = await pool.PutClientCutObjectAsync(param, default).ConfigureAwait(true); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - 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 @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await CheckFilter(pool, containerId, new FilterByRootObject()); - - await Cleanup(pool); - - await foreach (var cid in pool.ListContainersAsync(default, default)) - { - Assert.Fail($"Container {cid.GetValue()} exist"); - } - } -} diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs deleted file mode 100644 index 7de8155..0000000 --- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/Multithread/MultithreadSmokeClientTests.cs +++ /dev/null @@ -1,684 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; - -using FrostFS.SDK.Client; -using FrostFS.SDK.Client.Interfaces; -using FrostFS.SDK.Cryptography; -using FrostFS.SDK.SmokeTests; - -namespace FrostFS.SDK.Tests.Smoke; - -[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class MultithreadSmokeClientTests : SmokeTestsBase -{ - [Fact] - public async void AccountTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var result = await client.GetBalanceAsync(default); - - Assert.NotNull(result); - Assert.True(result.Value == 0); - } - - [Fact] - public async void NetworkMapTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var result = await client.GetNetmapSnapshotAsync(default); - - Assert.True(result.Epoch > 0); - Assert.Single(result.NodeInfoCollection); - - var item = result.NodeInfoCollection[0]; - Assert.Equal(2, item.Version.Major); - Assert.Equal(13, item.Version.Minor); - Assert.Equal(NodeState.Online, item.State); - Assert.True(item.PublicKey.Length > 0); - Assert.Single(item.Addresses); - Assert.Equal(9, item.Attributes.Count); - } - - - [Fact] - public async void NodeInfoTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var result = await client.GetNodeInfoAsync(default); - - 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); - } - - [Fact] - public async void NodeInfoStatisticsTest() - { - var options = ClientOptions; - - var callbackContent = string.Empty; - options.Value.Callback = (cs) => callbackContent = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - var result = await client.GetNodeInfoAsync(default); - - Assert.NotEmpty(callbackContent); - } - - [Fact] - public async void GetSessionTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - var token = await client.CreateSessionAsync(new(100), default); - - Assert.NotNull(token); - Assert.NotEqual(Guid.Empty, token.Id); - Assert.Equal(33, token.SessionKey.Length); - } - - [Fact] - public async void CreateObjectWithSessionToken() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["key1", "value1"]); - - var containerId = await client.PutContainerAsync(createContainerParam, default); - - var bytes = GetRandomBytes(1024); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default) - .ConfigureAwait(true); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk().ConfigureAwait(true)) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client).ConfigureAwait(true); - } - - [Fact] - public async void FilterTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await client.PutContainerAsync(createContainerParam, default); - - 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())); - - 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); - - await CheckFilter(client, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); - - await CheckFilter(client, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - - await CheckFilter(client, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); - - await CheckFilter(client, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); - - await CheckFilter(client, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); - - await CheckFilter(client, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - - await CheckFilter(client, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - - await CheckFilter(client, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); - - var checkSum = CheckSum.CreateCheckSum(bytes); - - await CheckFilter(client, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); - - await CheckFilter(client, containerId, new FilterByPhysicallyStored()); - } - - private static async Task CheckFilter(IFrostFSClient client, FrostFsContainerId containerId, IObjectFilter filter) - { - var resultObjectsCount = 0; - - PrmObjectSearch searchParam = new(containerId, null, [], filter); - - await foreach (var objId in client.SearchObjectsAsync(searchParam, default)) - { - resultObjectsCount++; - var objHeader = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); - } - - Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioTest(int objectSize) - { - bool callbackInvoked = false; - - var options = ClientOptions; - - options.Value.Callback = new((cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }); - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - await Cleanup(client); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await client.PutContainerAsync(createContainerParam, ctx); - - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - Assert.True(callbackInvoked); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) - { - hasObject = true; - - var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void PatchTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await client.PutContainerAsync(createContainerParam, default); - - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - - var bytes = new byte[1024]; - for (int i = 0; i < 1024; i++) - { - bytes[i] = 31; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var patch = new byte[16]; - for (int i = 0; i < 16; i++) - { - patch[i] = 32; - } - - var range = new FrostFsRange(8, (ulong)patch.Length); - - var patchParams = new PrmObjectPatch( - new FrostFsAddress(createdContainer, objectId), - payload: new MemoryStream(patch), - maxChunkLength: 32, - range: range); - - var newIbjId = await client.PatchObjectAsync(patchParams, default); - - var @object = await client.GetObjectAsync(new PrmObjectGet(createdContainer, newIbjId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - for (int i = 0; i < (int)range.Offset; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); - - var rangeEnd = range.Offset + range.Length; - - for (int i = (int)range.Offset; i < (int)rangeEnd; i++) - Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); - - for (int i = (int)rangeEnd; i < bytes.Length; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void RangeTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await client.PutContainerAsync(createContainerParam, default); - - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - - var bytes = new byte[256]; - for (int i = 0; i < 256; i++) - { - bytes[i] = (byte)i; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader(containerId: createdContainer, type: FrostFsObjectType.Regular)); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var rangeParam = new PrmRangeGet(createdContainer, objectId, new FrostFsRange(100, 64)); - - var rangeReader = await client.GetRangeAsync(rangeParam, default); - - var downloadedBytes = new byte[rangeParam.Range.Length]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await rangeReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes.AsSpan().Slice(100, 64)), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Fact] - public async void RangeHashTest() - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await client.PutContainerAsync(createContainerParam, default); - - var container = await client.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - - var bytes = new byte[256]; - for (int i = 0; i < 256; i++) - { - bytes[i] = (byte)i; - } - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular)); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var rangeParam = new PrmRangeHashGet(createdContainer, objectId, [new FrostFsRange(100, 64)], bytes); - - var hashes = await client.GetRangeHashAsync(rangeParam, default); - - foreach (var hash in hashes) - { - var x = hash[..32].ToArray(); - } - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioWithSessionTest(int objectSize) - { - var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); - //Callback = new((CallStatistics cs) => Assert.True(cs.ElapsedMicroSeconds > 0)) - var token = await client.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - await Cleanup(client); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams); - - var container = await client.PutContainerAsync(createContainerParam, ctx); - - var containerInfo = await client.GetContainerAsync(new PrmContainerGet(container), ctx); - Assert.NotNull(containerInfo); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: container, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await client.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in client.SearchObjectsAsync( - new PrmObjectSearch(container, token, [], filter), default)) - { - hasObject = true; - - var res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await client.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(client); - - await foreach (var _ in client.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB - [InlineData(64 * 1024 * 1024 - 1)] - [InlineData(64 * 1024 * 1024 + 1)] - [InlineData(2 * 64 * 1024 * 1024 + 256)] - [InlineData(200)] - public async void ClientCutScenarioTest(int objectSize) - { - var options = ClientOptions; - options.Value.Interceptors.Add(new CallbackInterceptor()); - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - await Cleanup(client); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await client.PutContainerAsync(createContainerParam, default); - - var ctx = new CallContext(TimeSpan.FromSeconds(10)); - - var container = await client.GetContainerAsync(new PrmContainerGet(containerId), ctx); - - Assert.NotNull(container); - - byte[] bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectClientCutPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes)); - - 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 res = await client.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - 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 @object = await client.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await CheckFilter(client, containerId, new FilterByRootObject()); - - await Cleanup(client); - - var deadline = DateTime.UtcNow.Add(TimeSpan.FromSeconds(5)); - - IAsyncEnumerator? enumerator = null; - do - { - if (deadline <= DateTime.UtcNow) - { - Assert.Fail("Containers exist"); - break; - } - - enumerator = client.ListContainersAsync(default, default).GetAsyncEnumerator(); - await Task.Delay(500); - } - while (await enumerator!.MoveNextAsync()); - } - - [Fact] - public async void NodeInfoCallbackAndInterceptorTest() - { - bool callbackInvoked = false; - bool intercepterInvoked = false; - - var options = ClientOptions; - options.Value.Callback = (cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }; - - options.Value.Interceptors.Add(new CallbackInterceptor(s => intercepterInvoked = true)); - - var client = FrostFSClient.GetInstance(options, GrpcChannel); - - var result = await client.GetNodeInfoAsync(default); - - Assert.True(callbackInvoked); - Assert.True(intercepterInvoked); - - 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); - } -} diff --git a/src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs b/src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs deleted file mode 100644 index 9e7ea80..0000000 --- a/src/FrostFS.SDK.Tests/Smoke/PoolTests/PoolSmokeTests.cs +++ /dev/null @@ -1,546 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; - -using FrostFS.SDK.Client; -using FrostFS.SDK.Cryptography; - -namespace FrostFS.SDK.Tests.Smoke; - -[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -[SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] -public class PoolSmokeTests : SmokeTestsBase -{ - private InitParameters GetDefaultParams() - { - return new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) - { - Key = keyString.LoadWif(), - NodeParams = [new(1, url, 100.0f)], - ClientBuilder = null, - GracefulCloseOnSwitchTimeout = 30_000_000, - Logger = null - }; - } - - [Fact] - public async void NetworkMapTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)); - - Assert.Null(error); - - var result = await pool.GetNetmapSnapshotAsync(default); - - Assert.True(result.Epoch > 0); - Assert.Single(result.NodeInfoCollection); - - var item = result.NodeInfoCollection[0]; - Assert.Equal(2, item.Version.Major); - Assert.Equal(13, item.Version.Minor); - Assert.Equal(NodeState.Online, item.State); - Assert.True(item.PublicKey.Length > 0); - Assert.Single(item.Addresses); - Assert.Equal(9, item.Attributes.Count); - } - - [Fact] - public async void NodeInfoTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - 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); - } - - [Fact] - public async void NodeInfoStatisticsTwoNodesTest() - { - var callbackText = string.Empty; - - var options = new InitParameters((url) => Grpc.Net.Client.GrpcChannel.ForAddress(new Uri(url))) - { - Key = keyString.LoadWif(), - NodeParams = [ - new(1, url, 100.0f), - new(2, url.Replace('0', '1'), 100.0f) - ], - ClientBuilder = null, - GracefulCloseOnSwitchTimeout = 30_000_000, - Logger = null, - Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds" - }; - - var pool = new Pool(options); - - var ctx = new CallContext(TimeSpan.Zero); - - var error = await pool.Dial(ctx).ConfigureAwait(true); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - var statistics = pool.Statistic(); - - Assert.False(string.IsNullOrEmpty(callbackText)); - Assert.Contains(" took ", callbackText, StringComparison.Ordinal); - } - - [Fact] - public async void NodeInfoStatisticsTest() - { - var options = GetDefaultParams(); - - var callbackText = string.Empty; - options.Callback = (cs) => callbackText = $"{cs.MethodName} took {cs.ElapsedMicroSeconds} microseconds"; - - var pool = new Pool(options); - - var ctx = new CallContext(TimeSpan.Zero); - - var error = await pool.Dial(ctx).ConfigureAwait(true); - - Assert.Null(error); - - var result = await pool.GetNodeInfoAsync(default); - - Assert.False(string.IsNullOrEmpty(callbackText)); - Assert.Contains(" took ", callbackText, StringComparison.Ordinal); - } - - [Fact] - public async void GetSessionTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - var prm = new PrmSessionCreate(100); - - var token = await pool.CreateSessionAsync(prm, default).ConfigureAwait(true); - - var ownerHash = Base58.Decode(OwnerId!.Value); - - Assert.NotNull(token); - Assert.NotEqual(Guid.Empty, token.Id); - Assert.Equal(33, token.SessionKey.Length); - } - - [Fact] - public async void CreateObjectWithSessionToken() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["key1", "value1"]); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - var bytes = GetRandomBytes(1024); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - } - - [Fact] - public async void FilterTest() - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - 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())); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var head = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var ecdsaKey = keyString.LoadWif(); - - var networkInfo = await pool.GetNetmapSnapshotAsync(default); - - await CheckFilter(pool, containerId, new FilterByContainerId(FrostFsMatchType.Equals, containerId)); - - await CheckFilter(pool, containerId, new FilterByOwnerId(FrostFsMatchType.Equals, FrostFsOwner.FromKey(ecdsaKey))); - - await CheckFilter(pool, containerId, new FilterBySplitId(FrostFsMatchType.Equals, param.Header!.Split!.SplitId)); - - await CheckFilter(pool, containerId, new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test")); - - await CheckFilter(pool, containerId, new FilterByObjectId(FrostFsMatchType.Equals, objectId)); - - await CheckFilter(pool, containerId, new FilterByVersion(FrostFsMatchType.Equals, networkInfo.NodeInfoCollection[0].Version)); - - await CheckFilter(pool, containerId, new FilterByEpoch(FrostFsMatchType.Equals, networkInfo.Epoch)); - - await CheckFilter(pool, containerId, new FilterByPayloadLength(FrostFsMatchType.Equals, 3)); - - var checkSum = CheckSum.CreateCheckSum(bytes); - - await CheckFilter(pool, containerId, new FilterByPayloadHash(FrostFsMatchType.Equals, checkSum)); - - await CheckFilter(pool, containerId, new FilterByPhysicallyStored()); - } - - private static async Task CheckFilter(Pool pool, FrostFsContainerId containerId, IObjectFilter filter) - { - var resultObjectsCount = 0; - - PrmObjectSearch searchParam = new(containerId, null, [], filter); - - await foreach (var objId in pool.SearchObjectsAsync(searchParam, default)) - { - resultObjectsCount++; - var objHeader = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objId), default); - } - - Assert.True(0 < resultObjectsCount, $"Filter for {filter.Key} doesn't work"); - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioTest(int objectSize) - { - bool callbackInvoked = false; - - var options = GetDefaultParams(); - - options.Callback = new((cs) => - { - callbackInvoked = true; - Assert.True(cs.ElapsedMicroSeconds > 0); - }); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var ctx = new CallContext(TimeSpan.Zero); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams, - xheaders: ["testKey", "testValue"]); - - var createdContainer = await pool.PutContainerAsync(createContainerParam, ctx); - - var container = await pool.GetContainerAsync(new PrmContainerGet(createdContainer), default); - Assert.NotNull(container); - Assert.True(callbackInvoked); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: createdContainer, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")])); - - var stream = await pool.PutObjectAsync(param, default).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(createdContainer, null, [], filter), default)) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(createdContainer, objectId), default); - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes!); - Assert.Equal("fileName", objHeader.Attributes!.First().Key); - Assert.Equal("test", objHeader.Attributes!.First().Value); - } - - Assert.True(hasObject); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(createdContainer, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - - await foreach (var _ in pool.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(3 * 1024 * 1024)] // exactly one chunk size - 3MB - [InlineData(6 * 1024 * 1024 + 100)] - public async void SimpleScenarioWithSessionTest(int objectSize) - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - options.Callback = new((cs) => Assert.True(cs.ElapsedMicroSeconds > 0)); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - var token = await pool.CreateSessionAsync(new PrmSessionCreate(int.MaxValue), default); - - await Cleanup(pool); - - var ctx = new CallContext(TimeSpan.FromSeconds(20)); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - PrmWait.DefaultParams); - - var container = await pool.PutContainerAsync(createContainerParam, ctx); - - var containerInfo = await pool.GetContainerAsync(new PrmContainerGet(container), ctx); - Assert.NotNull(containerInfo); - - var bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectPut( - new FrostFsObjectHeader( - containerId: container, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - sessionToken: token); - - var stream = await pool.PutObjectAsync(param, new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - await stream.WriteAsync(bytes.AsMemory()); - var objectId = await stream.CompleteAsync(); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - var objs = pool.SearchObjectsAsync(new PrmObjectSearch(container, token, [], filter), default); - await foreach (var objId in objs) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(container, objectId, false, token), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); - Assert.Equal((ulong)bytes.Length, objHeader.PayloadLength); - Assert.NotNull(objHeader.Attributes); - Assert.Single(objHeader.Attributes); - Assert.Equal("fileName", objHeader.Attributes.First().Key); - Assert.Equal("test", objHeader.Attributes.First().Value); - } - - Assert.True(hasObject); - - var @object = await pool.GetObjectAsync(new PrmObjectGet(container, objectId, token), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await Cleanup(pool); - - await foreach (var _ in pool.ListContainersAsync(default, default)) - { - Assert.Fail("Containers exist"); - } - } - - [Theory] - [InlineData(1)] - [InlineData(64 * 1024 * 1024)] // exactly 1 block size - 64MB - [InlineData(64 * 1024 * 1024 - 1)] - [InlineData(64 * 1024 * 1024 + 1)] - [InlineData(2 * 64 * 1024 * 1024 + 256)] - [InlineData(200)] - public async void ClientCutScenarioTest(int objectSize) - { - var options = GetDefaultParams(); - - var pool = new Pool(options); - - var error = await pool.Dial(new CallContext(TimeSpan.Zero)).ConfigureAwait(true); - - Assert.Null(error); - - await Cleanup(pool); - - var createContainerParam = new PrmContainerCreate( - new FrostFsContainerInfo(new FrostFsPlacementPolicy(true, 1, [], [], new FrostFsReplica(1))), - lightWait); - - var containerId = await pool.PutContainerAsync(createContainerParam, default); - - var ctx = new CallContext(TimeSpan.FromSeconds(10)); - - // ctx.Interceptors.Add(new CallbackInterceptor()); - - var container = await pool.GetContainerAsync(new PrmContainerGet(containerId), ctx); - - Assert.NotNull(container); - - byte[] bytes = GetRandomBytes(objectSize); - - var param = new PrmObjectClientCutPut( - new FrostFsObjectHeader( - containerId: containerId, - type: FrostFsObjectType.Regular, - [new FrostFsAttributePair("fileName", "test")]), - payload: new MemoryStream(bytes)); - - var objectId = await pool.PutClientCutObjectAsync(param, default).ConfigureAwait(true); - - var filter = new FilterByAttributePair(FrostFsMatchType.Equals, "fileName", "test"); - - bool hasObject = false; - await foreach (var objId in pool.SearchObjectsAsync(new PrmObjectSearch(containerId, null, [], filter), default)) - { - hasObject = true; - - var res = await pool.GetObjectHeadAsync(new PrmObjectHeadGet(containerId, objectId), default); - - var objHeader = res.HeaderInfo; - Assert.NotNull(objHeader); 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 @object = await pool.GetObjectAsync(new PrmObjectGet(containerId, objectId), default); - - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk = null; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) - { - ms.Write(chunk.Value.Span); - } - - Assert.Equal(SHA256.HashData(bytes), SHA256.HashData(downloadedBytes)); - - await CheckFilter(pool, containerId, new FilterByRootObject()); - - await Cleanup(pool); - - await foreach (var cid in pool.ListContainersAsync(default, default)) - { - Assert.Fail($"Container {cid.GetValue()} exist"); - } - } -} diff --git a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs index 59099a9..5e6998b 100644 --- a/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Smoke/SmokeTestsBase.cs @@ -16,19 +16,13 @@ namespace FrostFS.SDK.Tests.Smoke; [SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "No secure purpose")] public abstract class SmokeTestsBase { - // cluster Ori - // internal readonly string url = "http://10.78.128.207:8080"; - // internal readonly string keyString = "L4JWLdedUd4b21sriRHtCPGkjG2Mryz2AWLiVqTBSNyxxyAUcc7s"; - // cluster - internal readonly string url = "http://10.78.128.190:8080"; - internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; + // internal readonly string url = "http://10.78.128.190:8080"; + // internal readonly string keyString = "L47c3bunc6bJd7uEAfPUae2VkyupFR9nizoH6jfPonzQxijqH2Ba"; // WSL2 - // internal readonly string url = "http://172.29.238.97:8080"; - // internal readonly string keyString = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; // "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; - - //"KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + internal readonly string url = "http://172.29.238.97:8080"; // "http://172.20.8.23:8080"; + internal readonly string keyString = "KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK"; protected ECDsa? Key { get; } @@ -77,7 +71,7 @@ public abstract class SmokeTestsBase if (networkSettings.HomomorphicHashingDisabled) attributes.Add(new("__SYSTEM__DISABLE_HOMOMORPHIC_HASHING", "true")); - + var containerInfo = new FrostFsContainerInfo( new FrostFsPlacementPolicy(unique, backupFactor, selectors, filter, replicas), [.. attributes]); diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs index db1e193..cf4bdfa 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; - using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; @@ -20,13 +19,16 @@ public class ContainerTest : ContainerTestsBase Assert.NotNull(result); Assert.NotNull(result.GetValue()); - Assert.True(Base58.Encode(Mocker.ContainerGuid.ToBytes()) == result.GetValue()); + + var bytes = Mocker.ContainerGuid.ToByteArray(true); + + Assert.True(Base58.Encode(new Span(bytes)) == result.GetValue()); } [Fact] public async void GetContainerTest() { - var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + var cid = new FrostFsContainerId(Base58.Encode(new Span(Mocker.ContainerGuid.ToByteArray(true)))); var result = await GetClient().GetContainerAsync(new PrmContainerGet(cid), default); @@ -61,7 +63,7 @@ public class ContainerTest : ContainerTestsBase public async void DeleteContainerAsyncTest() { Mocker.ReturnContainerRemoved = true; - var cid = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + var cid = new FrostFsContainerId(Base58.Encode(new Span(Mocker.ContainerGuid.ToByteArray(true)))); await GetClient().DeleteContainerAsync(new PrmContainerDelete(cid, PrmWait.DefaultParams), default); diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 479c6ba..7a595e6 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -88,7 +88,7 @@ public class ObjectTest : ObjectTestsBase // PART1 Assert.Equal(blockSize, objects[0].Payload.Length); Assert.True(bytes.AsMemory(0, blockSize).ToArray().SequenceEqual(objects[0].Payload)); - + Assert.NotNull(objects[0].Header.Split.SplitId); Assert.Null(objects[0].Header.Split.Previous); Assert.True(objects[0].Header.Attributes.Count == 0); @@ -104,7 +104,7 @@ public class ObjectTest : ObjectTestsBase // last part Assert.Equal(bytes.Length % blockSize, objects[2].Payload.Length); - Assert.True(bytes.AsMemory(2*blockSize).ToArray().SequenceEqual(objects[2].Payload)); + Assert.True(bytes.AsMemory(2 * blockSize).ToArray().SequenceEqual(objects[2].Payload)); Assert.NotNull(objects[3].Header.Split.Parent); Assert.NotNull(objects[3].Header.Split.ParentHeader); diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs index 18e3981..c3681ce 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTestsBase.cs @@ -35,7 +35,7 @@ public abstract class ObjectTestsBase ContainerGuid = Guid.NewGuid() }; - ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToBytes())); + ContainerId = new FrostFsContainerId(Base58.Encode(Mocker.ContainerGuid.ToByteArray(true))); Mocker.ObjectHeader = new( ContainerId, diff --git a/src/FrostFS.SDK.Tests/Unit/SessionTests.cs b/src/FrostFS.SDK.Tests/Unit/SessionTests.cs index b2f1b78..be2e072 100644 --- a/src/FrostFS.SDK.Tests/Unit/SessionTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/SessionTests.cs @@ -2,7 +2,6 @@ using System.Diagnostics.CodeAnalysis; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; -using FrostFS.SDK.Cryptography; namespace FrostFS.SDK.Tests.Unit; @@ -38,7 +37,7 @@ public class SessionTest : SessionTestsBase Assert.NotNull(result); Assert.NotEqual(Guid.Empty, result.Id); - Assert.Equal(Mocker.SessionId, result.Id.ToBytes()); + Assert.Equal(Mocker.SessionId, result.Id.ToByteArray(true)); Assert.Equal(Mocker.SessionKey, result.SessionKey.ToArray()); //Assert.Equal(OwnerId.ToMessage(), result.Token.Body.OwnerId); From 45e73a6f8ea45fab645c3bea6b00f444c6e47986 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Mon, 31 Mar 2025 13:51:45 +0300 Subject: [PATCH 55/65] [#43] Client: Set nuget version Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 2 +- src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj | 2 +- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index e94d3f8..1d16492 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -6,7 +6,7 @@ enable AllEnabledByDefault FrostFS.SDK.Client - 1.0.3 + 1.0.4 C# SDK for FrostFS gRPC native protocol diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 9dfd370..04077e8 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Cryptography - 1.0.3 + 1.0.4 Cryptography tools for C# SDK diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 5207bbc..72bfe4c 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Protos - 1.0.3 + 1.0.4 Protobuf client for C# SDK From 30af6145584a40781695f5084978b78f81ba3460 Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Wed, 9 Apr 2025 17:09:12 +0300 Subject: [PATCH 56/65] [#57] Add helpers for signing Nuget packages Discussion: OBJECT-16744 Signed-off-by: Vitaliy Potyarkin --- .gitignore | 5 ++- Makefile | 58 ++++++++++++++++++++++++++++++++ release/README.md | 82 +++++++++++++++++++++++++++++++++++++++++++++ release/ca.cert | 34 +++++++++++++++++++ release/codesign.mk | 74 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 release/README.md create mode 100644 release/ca.cert create mode 100644 release/codesign.mk diff --git a/.gitignore b/.gitignore index ae0b81f..449284e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,8 @@ antlr-*.jar # binary bin/ -release/ obj/ + +# Repository signing keys +release/maintainer.* +release/ca.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f02478e --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +DOTNET?=dotnet +DOCKER?=docker + +NUGET_REGISTRY?=TrueCloudLab +NUGET_REGISTRY_URL?=https://git.frostfs.info/api/packages/TrueCloudLab/nuget/index.json +NUGET_REGISTRY_USER?= +NUGET_REGISTRY_PASSWORD?= + +NUPKG=find -iname '*.nupkg' | grep . | xargs -d'\n' -t -r -n1 +RFC3161_TSA?=http://timestamp.digicert.com + + +.PHONY: build +build: + $(DOTNET) build + + +.PHONY: sign +sign: export NUGET_CERT_REVOCATION_MODE=offline +sign: release/maintainer.pfx + $(NUPKG) $(DOTNET) nuget sign --overwrite --certificate-path $< --timestamper "$(RFC3161_TSA)" + @rm -v "$<" # maintainer.pfx is not password protected and must be ephemeral + $(NUPKG) $(DOTNET) nuget verify + + +.PHONY: publish +publish: + $(NUPKG) $(DOTNET) nuget verify + $(NUPKG) $(DOTNET) nuget push --source "$(NUGET_REGISTRY)" + + +.PHONY: nuget-registry +nuget-registry: +ifeq (,$(NUGET_REGISTRY_USER)) + $(error NUGET_REGISTRY_USER not set) +endif +ifeq (,$(NUGET_REGISTRY_PASSWORD)) + $(error NUGET_REGISTRY_PASSWORD not set) +endif + $(DOTNET) nuget add source \ + --name "$(NUGET_REGISTRY)" \ + --username "$(NUGET_REGISTRY_USER)" \ + --password "$(NUGET_REGISTRY_PASSWORD)" \ + --store-password-in-clear-text \ + "$(NUGET_REGISTRY_URL)" + + +.PHONY: clean +clean: + -$(NUPKG) rm -v + + +.PHONY: container +container: + $(DOCKER) run --pull=always --rm -it -v "$$PWD:/src" -w /src git.frostfs.info/truecloudlab/env:dotnet-8.0 + + +include release/codesign.mk diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000..96f8c25 --- /dev/null +++ b/release/README.md @@ -0,0 +1,82 @@ +# Release process + +## Preparing release + +_TBD_ + +## Trusting TrueCloudLab code signing CA certificate + +Verifying signatures (and signing) TrueCloudLab packages requires adding +[TrueCloudLab Code Signing CA](ca.cert) to the list of trusted roots. + +On Linux this can be done by appending [release/ca.cert](ca.cert) to one of: + +- `/etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem`: compatible with + [update-ca-trust] and originally proposed in [.NET design docs] +- `…/dotnet/sdk/X.Y.ZZZ/trustedroots/codesignctl.pem`: [fallback] codesigning certificate trust list for .NET + +[update-ca-trust]: https://www.linux.org/docs/man8/update-ca-trust.html +[.NET design docs]: https://github.com/dotnet/designs/blob/main/accepted/2021/signed-package-verification/re-enable-signed-package-verification-technical.md#linux +[fallback]: https://github.com/dotnet/sdk/blob/11150c0ec9020625308edeec555a8b78dbfb2aa5/src/Layout/redist/trustedroots/README.md + +## Signing Nuget packages + +Repository maintainer places `maintainer.cert` and `maintainer.key` (see below +regarding obtaining these files) into `release/` directory and then +executes: + +```console +$ make build sign +``` + +## Uploading packages to Nuget registry + +**IMPORTANT: the following steps upload all `*.nupkg` files located under +`src/`. Maintainer MUST make sure that no unnecessary package versions will be +uploaded to the registry.** + +Configure registry credentials (once per machine): + +```console +$ make nuget-registry NUGET_REGISTRY_USER=username NUGET_REGISTRY_PASSWORD=token +``` + +Publish all locally built packages (implicitly clear existing `*.nupkg` and +rebuild current version only): + +```console +$ make clean build sign publish +``` + + +## Obtaining release signing certificate + +Repository maintainer owns and keeps safe the release signing key +(`maintainer.key`). Private key should never leave maintainer's machine and +should be considered a highly sensitive secret. + +- Generating new maintainer key and the corresponding CSR: + + ```console + $ make maintainer.csr + ...lines skipped... + Enter PEM pass phrase: + Verifying - Enter PEM pass phrase: + ----- + IMPORTANT: Keep maintainer.key private! + + Certificate signing request is ready. + Send maintainer.csr to CA administrator to obtain the certificate. + ``` + + Resulting CSR (`maintainer.csr`) does not contain any sensitive + cryptographic material and may be passed to CA administrator through regular + communication channels. + +- CA administrator then issues the certificate (`make maintainer.cert`) and + sends it back to the maintainer to be used in combination with + `maintainer.key` + +This procedure should be repeated once per machine per `maintainer.cert` +lifetime (1 year) - typically just once per year since we expect the +maintainer to use only a single computer to sign releases. diff --git a/release/ca.cert b/release/ca.cert new file mode 100644 index 0000000..4f4c2f4 --- /dev/null +++ b/release/ca.cert @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF2zCCA8OgAwIBAgIUa9xC/RgvFtUG/xeR016nn0B4K0YwDQYJKoZIhvcNAQEL +BQAwdTELMAkGA1UEBhMCUlUxFTATBgNVBAoMDFRydWVDbG91ZExhYjEVMBMGA1UE +CwwMVHJ1ZUNsb3VkTGFiMTgwNgYDVQQDDC9UcnVlQ2xvdWRMYWIgQ29kZSBTaWdu +aW5nIENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0yNTA0MTAxNTI2MTFaFw0zNTA0 +MDgxNTI2MTFaMHUxCzAJBgNVBAYTAlJVMRUwEwYDVQQKDAxUcnVlQ2xvdWRMYWIx +FTATBgNVBAsMDFRydWVDbG91ZExhYjE4MDYGA1UEAwwvVHJ1ZUNsb3VkTGFiIENv +ZGUgU2lnbmluZyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCyANB4cjf+ZEAFx9RiUYXCAOPMV+jyqgcVbhzh2YKc +9SlvGKRlc00Ar1RlFcrycdkIrTKmhhobiWsFp7UgphwLqRTCb5NB6qfUoWhnfiD9 +m0OBgeVX5wivVaibRI9PSTbFDcIhYUiNvwFJ6GduH/9zOxf1BvuL7LMaoyhIDcg/ +XVLuekE2lnX83zsedv0v/2jyyMY9Ct6N2BXzyHSAzSdYYg0F9Qu9fIMAPjoKhWPc +PnotqaACjb1DScLUr3E/o2W1FfprTT2Pip/0AXxO4wixl4QWh9HeOKV22KRcCHo6 +3wNdg5q1ZVGTNBW0+yoB4jsSG8/JM+2Ujhc1ZnYH10armvGq/0Oc2YQE00960Wy8 +t0drCFWJUO1XHNeBxkkupmj7N1TAPbixtfiGZJhECOWOJwyMpcKixlt5P0cNH4N/ +p3vjyrGQxGLBIkgV/QgjfGkpTHKT1/H40YK6DliWJc01KfNTqn0K+/TIyF0n26kD +BWYVlvDh5P1+V9DGuD2zeXB3PstoifD/Pd7D8wuqpm17noFE19MLp94xv03q9nEa +jRMEd2J2buTLvMh5BBVH0Sm38QAHpSIZ9O3dSLvvjlALbVtwmcsNE9fgxiue3vTB +iXNW8wWs+/DMYwbWyBoCwORxVOdOyc1JLn7qAAEUBweilPVXpWuzMLdUsifPiqrV +dQIDAQABo2MwYTAdBgNVHQ4EFgQUEz4y/RvQMmbUFvf5JbGe/0PZR90wHwYDVR0j +BBgwFoAUEz4y/RvQMmbUFvf5JbGe/0PZR90wDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggIBAF79W9hMGnrKUWLDOcoeXDEn ++gZxd5rjeF0tNEQGbWKGTJAGQdprOkzWQ47PewpFeS+ePpjOglBAt7cV2ZnOugAT +Brx31vYpNAvYnUCYn+/IUqD8S/U4uErVS9zG9QjirZWo56eJP62vnScKuApCQCbA +pp0zrIyJ+2lQKzlMOENRqzYYA/UTOqYTtnW6x2D8goVqCmDXpgpxEp5XliFjJSr6 +dOjiopNWMvaV3R/Bnd4i41taM7M6HpIV+gbXmEKEFS0ejVfzT8z1pTigN7GBqbxf +nXD03eLUIsbMDv4ZQPrheN7nKnjRUn8kxz0SSK1m2YDrXW51m8fOs6aTvwC/cNe+ +FJMqQMF32i4IXVfUbyUJi+JMvawqm2wEY46vrh7siprY6rXsAzCKJo07i6jvUwnT +TXMldSCPgTEqzT2JBzzr0tRfuPKsv0/NqflHvwfuMRCpcZ7jJZ700iN92xXkiQHP +DmCZOILXcNclAth3nAnyY4XE5a8myv8bwYaPdJdIFlV+BoU/8mClDeA8ck4rDy12 +T5YChKew2oiL4j4B6v9/yrDjD1IT0gv4BWyPhb/n390BCEXt8g9auNcT0s6O8kEc +VUDVc1519ocMCuWVuqUK9o2w0zu50/pBn4hVLfT3QyW8sqtlRKghOWtqZzigvCWF +VjATeO5F/Z7OSDebHUGv +-----END CERTIFICATE----- diff --git a/release/codesign.mk b/release/codesign.mk new file mode 100644 index 0000000..1c39361 --- /dev/null +++ b/release/codesign.mk @@ -0,0 +1,74 @@ +PKI_ROLE?=maintainer +PKI_DIR?=release + +# Note: Only RSA signatures are supported (NU3013) +# https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3013) + + +ifeq ($(PKI_ROLE),maintainer) +.PHONY: maintainer.csr +maintainer.csr: $(PKI_DIR)/maintainer.csr +$(PKI_DIR)/maintainer.csr: KEY=$(patsubst %.csr,%.key,$@) +$(PKI_DIR)/maintainer.csr: + openssl req \ + -new \ + -newkey rsa:4096 \ + -keyout $(KEY) \ + -out $@ \ + -sha256 \ + -addext keyUsage=critical,digitalSignature \ + -addext extendedKeyUsage=critical,codeSigning,msCodeCom \ + -subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=frostfs-sdk-csharp Release Team" + @echo "IMPORTANT: Keep $(KEY) private!\n" + @echo "Certificate signing request is ready.\nSend $@ to CA administrator to obtain the certificate." + +$(PKI_DIR)/maintainer.pfx: $(PKI_DIR)/maintainer.cert $(PKI_DIR)/maintainer.key $(PKI_DIR)/ca.cert + openssl verify \ + -CAfile $(PKI_DIR)/ca.cert \ + $(PKI_DIR)/maintainer.cert + openssl pkcs12 \ + -export \ + -out $@ \ + -inkey $(PKI_DIR)/maintainer.key \ + -in $(PKI_DIR)/maintainer.cert \ + -CAfile $(PKI_DIR)/ca.cert \ + -chain \ + -passout pass: +endif + + +ifeq ($(PKI_ROLE),ca) +.PHONY: maintainer.cert +maintainer.cert: $(PKI_DIR)/maintainer.cert +$(PKI_DIR)/maintainer.cert: CSR=$(patsubst %.cert,%.csr,$@) +$(PKI_DIR)/maintainer.cert: $(PKI_DIR)/ca.key $(PKI_DIR)/ca.cert + openssl req -noout -text -in $(CSR) + @read -p "Review the CSR above. Press Enter to continue, Ctrl+C to cancel " -r null + openssl x509 \ + -req \ + -days 365 \ + -in $(CSR) \ + -copy_extensions copy \ + -ext keyUsage,extendedKeyUsage \ + -CA $(PKI_DIR)/ca.cert \ + -CAkey $(PKI_DIR)/ca.key \ + -CAcreateserial \ + -out $@ + echo >> $@ + cat $(PKI_DIR)/ca.cert >> $@ + openssl x509 -noout -text -in $@ -fingerprint -sha256 + @echo "Certificate is ready.\nSend $@ back to maintainer." + +$(PKI_DIR)/ca.key: CERT=$(patsubst %.key,%.cert,$@) +$(PKI_DIR)/ca.key: + openssl req \ + -x509 \ + -newkey rsa:4096 \ + -keyout $@ \ + -out $(CERT) \ + -sha256 \ + -days 3650 \ + -addext keyUsage=critical,keyCertSign \ + -subj "/C=RU/O=TrueCloudLab/OU=TrueCloudLab/CN=TrueCloudLab Code Signing Certificate Authority" + @echo "IMPORTANT: Keep $@ private!\n" +endif From b3907782012285b2b0c58da01829def467315e2a Mon Sep 17 00:00:00 2001 From: Vitaliy Potyarkin Date: Fri, 11 Apr 2025 10:33:19 +0300 Subject: [PATCH 57/65] [#57] ci: Disable automatic publishing of unsigned nugets We're switching to non-automatic process for publishing signed nugets, unsigned workflow will still be available as an escape hatch but it won't ever be triggered automatically. Signed-off-by: Vitaliy Potyarkin --- .forgejo/workflows/publish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.forgejo/workflows/publish.yml b/.forgejo/workflows/publish.yml index ca66daa..fd79900 100644 --- a/.forgejo/workflows/publish.yml +++ b/.forgejo/workflows/publish.yml @@ -1,5 +1,4 @@ on: - push: workflow_dispatch: jobs: @@ -16,7 +15,7 @@ jobs: # `dotnet build` implies and replaces `dotnet pack` thanks to `GeneratePackageOnBuild` run: dotnet build - - name: Publish NuGet packages + - name: Publish unsigned NuGet packages run: |- dotnet nuget add source \ --name "$NUGET_REGISTRY" \ From 42f45076384f40558f4bebdc23b38d08e3aeab08 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 3 Apr 2025 17:41:14 +0300 Subject: [PATCH 58/65] [#55] Fix Replica mapper Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs index cd7a0b7..00ad4a3 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs @@ -12,7 +12,9 @@ public static class PolicyMapper return new Replica { Count = (uint)replica.Count, - Selector = replica.Selector + Selector = replica.Selector, + EcDataCount = replica.EcDataCount, + EcParityCount = replica.EcParityCount }; } @@ -23,7 +25,11 @@ public static class PolicyMapper throw new ArgumentNullException(nameof(replica)); } - return new FrostFsReplica((int)replica.Count, replica.Selector); + return new FrostFsReplica((int)replica.Count, replica.Selector) + { + EcDataCount = replica.EcDataCount, + EcParityCount = replica.EcParityCount + }; } public static Selector ToMessage(this FrostFsSelector selector) From 5f451c881881a9297a184a4f8ad9798eeb59615b Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 11 Apr 2025 15:52:31 +0300 Subject: [PATCH 59/65] [#60] Wallet tools in SDK Signed-off-by: Pavel Gross --- .../FrostFS.SDK.Client.csproj | 1 + src/FrostFS.SDK.Client/Tools/WalletTools.cs | 31 ++++++ src/FrostFS.SDK.Client/Wallets/Account.cs | 24 +++++ src/FrostFS.SDK.Client/Wallets/Contract.cs | 15 +++ src/FrostFS.SDK.Client/Wallets/Extra.cs | 6 ++ src/FrostFS.SDK.Client/Wallets/Parameter.cs | 12 +++ src/FrostFS.SDK.Client/Wallets/ScryptValue.cs | 15 +++ src/FrostFS.SDK.Client/Wallets/Wallet.cs | 18 ++++ src/FrostFS.SDK.Cryptography/Base58.cs | 2 +- .../FrostFS.SDK.Cryptography.csproj | 1 + src/FrostFS.SDK.Cryptography/Key.cs | 19 ++++ .../WalletExtractor.cs | 98 +++++++++++++++++++ src/FrostFS.SDK.Tests/TestData/wallet.json | 30 ++++++ src/FrostFS.SDK.Tests/Unit/WalletTests.cs | 25 +++++ 14 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 src/FrostFS.SDK.Client/Tools/WalletTools.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/Account.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/Contract.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/Extra.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/Parameter.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/ScryptValue.cs create mode 100644 src/FrostFS.SDK.Client/Wallets/Wallet.cs create mode 100644 src/FrostFS.SDK.Cryptography/WalletExtractor.cs create mode 100644 src/FrostFS.SDK.Tests/TestData/wallet.json create mode 100644 src/FrostFS.SDK.Tests/Unit/WalletTests.cs diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 1d16492..ed050a2 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -45,6 +45,7 @@ + diff --git a/src/FrostFS.SDK.Client/Tools/WalletTools.cs b/src/FrostFS.SDK.Client/Tools/WalletTools.cs new file mode 100644 index 0000000..45532a7 --- /dev/null +++ b/src/FrostFS.SDK.Client/Tools/WalletTools.cs @@ -0,0 +1,31 @@ +using System.Text.Json; +using System; +using FrostFS.SDK.Client.Wallets; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK.Client; + +public static class WalletTools +{ + public static string GetWifFromWallet(string walletJsonText, byte[] password, int accountIndex = 0) + { + var wallet = JsonSerializer.Deserialize(walletJsonText) ?? throw new ArgumentException("Wrong wallet format"); + + if (wallet.Accounts == null || wallet.Accounts.Length < accountIndex + 1) + { + throw new ArgumentException("Wrong wallet content"); + } + + var encryptedKey = wallet.Accounts[accountIndex].Key; + + if (string.IsNullOrEmpty(encryptedKey)) + { + throw new ArgumentException("Cannot get encrypted WIF"); + } + + var privateKey = CryptoWallet.GetKeyFromEncodedWif(password, encryptedKey!); + var wif = privateKey.GetWIFFromPrivateKey(); + + return wif; + } +} diff --git a/src/FrostFS.SDK.Client/Wallets/Account.cs b/src/FrostFS.SDK.Client/Wallets/Account.cs new file mode 100644 index 0000000..1d5b3eb --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/Account.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace FrostFS.SDK.Client.Wallets; + +public class Account +{ + [JsonPropertyName("address")] + public string? Address { get; set; } + + [JsonPropertyName("key")] + public string? Key { get; set; } + + [JsonPropertyName("label")] + public string? Label { get; set; } + + [JsonPropertyName("contract")] + public Contract? Contract { get; set; } + + [JsonPropertyName("lock")] + public bool Lock { get; set; } + + [JsonPropertyName("isDefault")] + public bool IsDefault { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Wallets/Contract.cs b/src/FrostFS.SDK.Client/Wallets/Contract.cs new file mode 100644 index 0000000..b15d21d --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/Contract.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace FrostFS.SDK.Client.Wallets; + +public class Contract +{ + [JsonPropertyName("script")] + public string? Script { get; set; } + + [JsonPropertyName("parameters")] + public Parameter[]? Parameters { get; set; } + + [JsonPropertyName("deployed")] + public bool Deployed { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Wallets/Extra.cs b/src/FrostFS.SDK.Client/Wallets/Extra.cs new file mode 100644 index 0000000..bff8b8d --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/Extra.cs @@ -0,0 +1,6 @@ +namespace FrostFS.SDK.Client.Wallets; + +public class Extra +{ + public string? Tokens { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Wallets/Parameter.cs b/src/FrostFS.SDK.Client/Wallets/Parameter.cs new file mode 100644 index 0000000..c831e36 --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/Parameter.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace FrostFS.SDK.Client.Wallets; + +public class Parameter +{ + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Wallets/ScryptValue.cs b/src/FrostFS.SDK.Client/Wallets/ScryptValue.cs new file mode 100644 index 0000000..2548b32 --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/ScryptValue.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace FrostFS.SDK.Client.Wallets; + +public class ScryptValue +{ + [JsonPropertyName("n")] + public int N { get; set; } + + [JsonPropertyName("r")] + public int R { get; set; } + + [JsonPropertyName("p")] + public int P { get; set; } +} diff --git a/src/FrostFS.SDK.Client/Wallets/Wallet.cs b/src/FrostFS.SDK.Client/Wallets/Wallet.cs new file mode 100644 index 0000000..e1439e0 --- /dev/null +++ b/src/FrostFS.SDK.Client/Wallets/Wallet.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace FrostFS.SDK.Client.Wallets; + +public class Wallet +{ + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("accounts")] + public Account[]? Accounts { get; set; } + + [JsonPropertyName("scrypt")] + public ScryptValue? Scrypt { get; set; } + + [JsonPropertyName("extra")] + public Extra? Extra { get; set; } +} diff --git a/src/FrostFS.SDK.Cryptography/Base58.cs b/src/FrostFS.SDK.Cryptography/Base58.cs index 0026bc3..4dc6187 100644 --- a/src/FrostFS.SDK.Cryptography/Base58.cs +++ b/src/FrostFS.SDK.Cryptography/Base58.cs @@ -32,7 +32,7 @@ public static class Base58 public static string Base58CheckEncode(this Span data) { - byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(data).AsSpan()); ; + byte[] checksum = DataHasher.Sha256(DataHasher.Sha256(data).AsSpan()); Span buffer = stackalloc byte[data.Length + 4]; data.CopyTo(buffer); diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 04077e8..43f7187 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -35,6 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/FrostFS.SDK.Cryptography/Key.cs b/src/FrostFS.SDK.Cryptography/Key.cs index 5f382fa..9eca104 100644 --- a/src/FrostFS.SDK.Cryptography/Key.cs +++ b/src/FrostFS.SDK.Cryptography/Key.cs @@ -82,6 +82,25 @@ public static class KeyExtension return DataHasher.Sha256(script.AsSpan()).RIPEMD160(); } + public static string GetWIFFromPrivateKey(this byte[] privateKey) + { + if (privateKey == null || privateKey.Length != 32) + { + throw new ArgumentNullException(nameof(privateKey)); + } + + Span wifSpan = stackalloc byte[34]; + + wifSpan[0] = 0x80; + wifSpan[33] = 0x01; + + privateKey.AsSpan().CopyTo(wifSpan.Slice(1)); + + var wif = Base58.Base58CheckEncode(wifSpan); + + return wif; + } + private static string ToAddress(this byte[] scriptHash, byte version) { Span data = stackalloc byte[21]; diff --git a/src/FrostFS.SDK.Cryptography/WalletExtractor.cs b/src/FrostFS.SDK.Cryptography/WalletExtractor.cs new file mode 100644 index 0000000..3e940f5 --- /dev/null +++ b/src/FrostFS.SDK.Cryptography/WalletExtractor.cs @@ -0,0 +1,98 @@ +using System; +using System.Text; +using System.Linq; +using System.Security.Cryptography; +using Org.BouncyCastle.Crypto.Generators; + +namespace FrostFS.SDK.Cryptography; + +public static class CryptoWallet +{ + private static int N = 16384; + private static int R = 8; + private static int P = 8; + + private enum CipherAction + { + Encrypt, + Decrypt + } + + public static byte[] GetKeyFromEncodedWif(byte[] password, string encryptedWIF) + { + var nep2Data = Base58.Base58CheckDecode(encryptedWIF); + + if (nep2Data.Length == 39 && nep2Data[0] == 1 && nep2Data[1] == 66 && nep2Data[2] == 224) + { + var addressHash = nep2Data.AsSpan(3, 4).ToArray(); + var derivedKey = SCrypt.Generate(password, addressHash, N, R, P, 64); + var derivedKeyHalf1 = derivedKey.Take(32).ToArray(); + var derivedKeyHalf2 = derivedKey.Skip(32).ToArray(); + var encrypted = nep2Data.AsSpan(7, 32).ToArray(); + var decrypted = Aes(encrypted, derivedKeyHalf2, CipherAction.Decrypt); + var plainPrivateKey = XorRange(decrypted, derivedKeyHalf1, 0, decrypted.Length); + + return plainPrivateKey; + } + else + { + throw new ArgumentException("Not valid NEP2 prefix."); + } + } + + public static string EncryptWif(string plainText, ECDsa key) + { + var addressHash = GetAddressHash(key); + var derivedKey = SCrypt.Generate(Encoding.UTF8.GetBytes(plainText), addressHash, N, R, P, 64); + var derivedHalf1 = derivedKey.Take(32).ToArray(); + var derivedHalf2 = derivedKey.Skip(32).ToArray(); + var encryptedHalf1 = Aes(XorRange(key.PrivateKey(), derivedHalf1, 0, 16), derivedHalf2, CipherAction.Encrypt); + var encryptedHalf2 = Aes(XorRange(key.PrivateKey(), derivedHalf1, 16, 32), derivedHalf2, CipherAction.Encrypt); + var prefixes = new byte[] { 1, 66, 224 }; + var concatenation = ArrayHelper.Concat([prefixes, addressHash, encryptedHalf1, encryptedHalf2]); + + return Base58.Base58CheckEncode(concatenation); + } + + public static byte[] GetAddressHash(ECDsa key) + { + string address = key.PublicKey().PublicKeyToAddress(); + + using SHA256 sha256 = SHA256.Create(); + byte[] addressHashed = sha256.ComputeHash(Encoding.UTF8.GetBytes(address)); + return [.. addressHashed.Take(4)]; + } + + private static byte[] XorRange(byte[] arr1, byte[] arr2, int from, int length) + { + byte[] result = new byte[length]; + + var j = 0; + for (var i = from; i < length; ++i) + { + result[j++] = (byte)(arr1[i] ^ arr2[i]); + } + + return result; + } + + private static byte[] Aes(byte[] data, byte[] key, CipherAction action) + { + using var aes = System.Security.Cryptography.Aes.Create(); + aes.Key = key; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.None; + + if (action == CipherAction.Encrypt) + { + return aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length); + } + + if (action == CipherAction.Decrypt) + { + return aes.CreateDecryptor().TransformFinalBlock(data, 0, data.Length); + } + + throw new ArgumentException("Wrong cippher action", nameof(action)); + } +} diff --git a/src/FrostFS.SDK.Tests/TestData/wallet.json b/src/FrostFS.SDK.Tests/TestData/wallet.json new file mode 100644 index 0000000..7a4bdd4 --- /dev/null +++ b/src/FrostFS.SDK.Tests/TestData/wallet.json @@ -0,0 +1,30 @@ +{ + "version": "1.0", + "accounts": [ + { + "address": "NWeByJPgNC97F83hTUnSbnZSBKaFvk5HNw", + "key": "6PYVCcS2yp89JpcfR61FGhdhhzyYjSErNedmpZErnybNTxUZMRdhzJLrek", + "label": "", + "contract": { + "script": "DCEDJOdiiPy5ABANAYAqFO+XfMpFrQc1YSMERt8Us0TIWLZBVuezJw==", + "parameters": [ + { + "name": "parameter0", + "type": "Signature" + } + ], + "deployed": false + }, + "lock": false, + "isDefault": false + } + ], + "scrypt": { + "n": 16384, + "r": 8, + "p": 8 + }, + "extra": { + "Tokens": null + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/WalletTests.cs b/src/FrostFS.SDK.Tests/Unit/WalletTests.cs new file mode 100644 index 0000000..2acd77e --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/WalletTests.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; +using FrostFS.SDK.Client; + +namespace FrostFS.SDK.Tests.Unit; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +public class WalletTest : SessionTestsBase +{ + [Fact] + public void TestWallet() + { + var password = Encoding.UTF8.GetBytes(""); + + var d = Directory.GetCurrentDirectory(); + var path = ".\\..\\..\\..\\TestData\\wallet.json"; + Assert.True(File.Exists(path)); + + var content = File.ReadAllText(path); + + var wif = WalletTools.GetWifFromWallet(content, password); + + Assert.Equal("KzPXA6669m2pf18XmUdoR8MnP1pi1PMmefiFujStVFnv7WR5SRmK", wif); + } +} \ No newline at end of file From c88eea1f8234bf980bfc1cb362c0f019bec3485f Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Fri, 11 Apr 2025 15:18:18 +0300 Subject: [PATCH 60/65] [#58] Observable client cut Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFSClient.cs | 3 +- .../Mappers/Object/ObjectHeaderMapper.cs | 5 + .../Models/Object/PartUploadedEventArgs.cs | 8 + .../Models/Object/UploadInfo.cs | 38 +++ .../Models/Object/UploadProgressInfo.cs | 48 +++ .../Parameters/PrmObjectClientCutPut.cs | 5 +- .../Services/ObjectServiceProvider.cs | 277 ++++++++++++++---- 7 files changed, 328 insertions(+), 56 deletions(-) create mode 100644 src/FrostFS.SDK.Client/Models/Object/PartUploadedEventArgs.cs create mode 100644 src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs create mode 100644 src/FrostFS.SDK.Client/Models/Object/UploadProgressInfo.cs diff --git a/src/FrostFS.SDK.Client/FrostFSClient.cs b/src/FrostFS.SDK.Client/FrostFSClient.cs index 860d346..bce9104 100644 --- a/src/FrostFS.SDK.Client/FrostFSClient.cs +++ b/src/FrostFS.SDK.Client/FrostFSClient.cs @@ -254,7 +254,8 @@ public class FrostFSClient : IFrostFSClient public Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { - return GetObjectService().PutClientCutObjectAsync(args, ctx); + return GetObjectService().PutClientCutSingleObjectAsync(args, ctx); + // return GetObjectService().PutClientCutObjectAsync(args, ctx); } public Task PutSingleObjectAsync(PrmSingleObjectPut args, CallContext ctx) diff --git a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs index 115f9e2..fde13ff 100644 --- a/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs +++ b/src/FrostFS.SDK.Client/Mappers/Object/ObjectHeaderMapper.cs @@ -44,6 +44,11 @@ public static class ObjectHeaderMapper public static FrostFsSplit ToModel(this Header.Types.Split split) { + if (split is null) + { + throw new ArgumentNullException(nameof(split)); + } + var children = split!.Children.Count != 0 ? new ReadOnlyCollection([.. split.Children.Select(x => x.ToModel())]) : null; diff --git a/src/FrostFS.SDK.Client/Models/Object/PartUploadedEventArgs.cs b/src/FrostFS.SDK.Client/Models/Object/PartUploadedEventArgs.cs new file mode 100644 index 0000000..16ad326 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Object/PartUploadedEventArgs.cs @@ -0,0 +1,8 @@ +using System; + +namespace FrostFS.SDK.Client; + +public class PartUploadedEventArgs(ObjectPartInfo part) : EventArgs +{ + public ObjectPartInfo Part { get; } = part; +} diff --git a/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs b/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs new file mode 100644 index 0000000..748fdfe --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs @@ -0,0 +1,38 @@ +using FrostFS.SDK; + +namespace FrostFS.SDK.Client; + +public readonly struct ObjectPartInfo(long offset, int length, FrostFsObjectId objectId) : System.IEquatable +{ + public long Offset { get; } = offset; + public int Length { get; } = length; + public FrostFsObjectId ObjectId { get; } = objectId; + + public override bool Equals(object obj) + { + if (obj == null || obj is not ObjectPartInfo) + return false; + + return Equals((ObjectPartInfo)obj); + } + + public override int GetHashCode() + { + return ((int)(Offset >> 32)) ^ (int)Offset ^ Length ^ ObjectId.Value.GetHashCode(); + } + + public static bool operator ==(ObjectPartInfo left, ObjectPartInfo right) + { + return left.Equals(right); + } + + public static bool operator !=(ObjectPartInfo left, ObjectPartInfo right) + { + return !(left == right); + } + + public bool Equals(ObjectPartInfo other) + { + return GetHashCode() == other.GetHashCode(); + } +} diff --git a/src/FrostFS.SDK.Client/Models/Object/UploadProgressInfo.cs b/src/FrostFS.SDK.Client/Models/Object/UploadProgressInfo.cs new file mode 100644 index 0000000..a6fa905 --- /dev/null +++ b/src/FrostFS.SDK.Client/Models/Object/UploadProgressInfo.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace FrostFS.SDK.Client; + +public class UploadProgressInfo +{ + private List _parts; + + public UploadProgressInfo(Guid splitId, int capacity = 8) + { + _parts = new List(capacity); + SplitId = new SplitId(splitId); + } + + public UploadProgressInfo(Guid splitId, Collection parts) + { + _parts = [.. parts]; + SplitId = new SplitId(splitId); + } + + public event EventHandler? Notify; + + public SplitId SplitId { get; } + + internal void AddPart(ObjectPartInfo part) + { + _parts.Add(part); + Notify?.Invoke(this, new PartUploadedEventArgs(part)); + } + + public ObjectPartInfo GetPart(int index) + { + return _parts[index]; + } + + public ObjectPartInfo GetLast() + { + return _parts.LastOrDefault(); + } + + public ReadOnlyCollection GetParts() + { + return new ReadOnlyCollection(_parts); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs b/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs index 706e367..46f25e0 100644 --- a/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs +++ b/src/FrostFS.SDK.Client/Parameters/PrmObjectClientCutPut.cs @@ -8,7 +8,8 @@ public readonly struct PrmObjectClientCutPut( int bufferMaxSize = 0, FrostFsSessionToken? sessionToken = null, byte[]? customBuffer = null, - string[]? xheaders = null) : PrmObjectPutBase, System.IEquatable + string[]? xheaders = null, + UploadProgressInfo? progress = null) : PrmObjectPutBase, System.IEquatable { /// /// Need to provide values like ContainerId and ObjectType to create and object. @@ -41,6 +42,8 @@ public readonly struct PrmObjectClientCutPut( /// public string[] XHeaders { get; } = xheaders ?? []; + public UploadProgressInfo? Progress { get; } = progress; + internal PutObjectContext PutObjectContext { get; } = new(); public override readonly bool Equals(object obj) diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index ea3b727..ed9e9bc 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -385,30 +385,202 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return response.Body.ObjectId.ToModel(); } - internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) + internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { - var stream = args.Payload!; - var header = args.Header!; + if (args.Payload == null) + throw new ArgumentException(nameof(args.Payload)); - if (header.PayloadLength > 0) - args.PutObjectContext.FullLength = header.PayloadLength; - else if (stream.CanSeek) - args.PutObjectContext.FullLength = (ulong)stream.Length; - else - throw new ArgumentException("The stream does not have a length and payload length is not defined"); - - if (args.PutObjectContext.FullLength == 0) - throw new ArgumentException("The stream has zero length"); + if (args.Header == null) + throw new ArgumentException(nameof(args.Header)); var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); - var partSize = (int)networkSettings.MaxObjectSize; + int partSize = (int)networkSettings.MaxObjectSize; - var restBytes = args.PutObjectContext.FullLength; + int chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; - var objectSize = (int)Math.Min((ulong)partSize, restBytes); + ulong fullLength; - // define collection capacity - var objectsCount = (int)(restBytes / (ulong)objectSize) + ((restBytes % (ulong)objectSize) > 0 ? 1 : 0); + // Information about the uploaded parts. + var progressInfo = args.Progress; // + var offset = 0L; + + if (progressInfo != null && progressInfo.GetParts().Count > 0) + { + if (!args.Payload.CanSeek) + { + throw new FrostFsException("Cannot resume client cut upload for this stream. Seek must be supported."); + } + + var lastPart = progressInfo.GetLast(); + args.Payload.Position = lastPart.Offset + lastPart.Length; + fullLength = (ulong)(args.Payload.Length - args.Payload.Position); + offset = args.Payload.Position; + } + else + { + if (args.Header.PayloadLength > 0) + fullLength = args.Header.PayloadLength; + else if (args.Payload.CanSeek) + fullLength = (ulong)args.Payload.Length; + else + throw new ArgumentException("The stream does not have a length and payload length is not defined"); + } + + //define collection capacity + var restPart = (fullLength % (ulong)partSize) > 0 ? 1 : 0; + var objectsCount = fullLength > 0 ? (int)(fullLength / (ulong)partSize) + restPart : 0; + + progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); + + var remain = fullLength; + + byte[]? buffer = null; + bool isRentBuffer = false; + + try + { + if (args.CustomBuffer != null) + { + if (args.CustomBuffer.Length < chunkSize) + throw new ArgumentException($"Buffer size is too small. At least {chunkSize} required"); + + buffer = args.CustomBuffer; + } + else + { + buffer = ArrayPool.Shared.Rent(chunkSize); + isRentBuffer = true; + } + + FrostFsObjectId? resultObjectId = null; + FrostFsObjectHeader? parentHeader = null; + + while (remain > 0) + { + var bytesToWrite = Math.Min((ulong)partSize, remain); + var isLastPart = remain <= (ulong)partSize; + + // When the last part of the object is uploaded, all metadata for the object must be added + if (isLastPart && objectsCount > 1) + { + parentHeader = new FrostFsObjectHeader(args.Header.ContainerId, FrostFsObjectType.Regular) + { + Attributes = args.Header.Attributes, + PayloadLength = fullLength + }; + } + + // Uploading the next part of the object. Note: the request must contain a non-null SplitId parameter + var header = objectsCount == 1 ? args.Header : new FrostFsObjectHeader( + args.Header.ContainerId, + FrostFsObjectType.Regular, + [], + new FrostFsSplit(progressInfo.SplitId, progressInfo.GetLast().ObjectId, parentHeader: parentHeader)); + + var prm = new PrmObjectPut(header); + using var stream = await PutStreamObjectAsync(prm, ctx).ConfigureAwait(false); + var uploaded = 0; + + // If an error occurs while uploading a part of the object, there is no need to re-upload the parts + // that were successfully uploaded before. It is sufficient to re-upload only the failed part + + var thisPartRest = (int)Math.Min((ulong)partSize, remain); + while (thisPartRest > 0) + { + var nextChunkSize = Math.Min(thisPartRest, chunkSize); + var size = await args.Payload.ReadAsync(buffer, 0, nextChunkSize).ConfigureAwait(false); + + if (size == 0) + break; + + await stream.WriteAsync(buffer.AsMemory(0, size)).ConfigureAwait(false); + uploaded += size; + thisPartRest -= size; + } + + var objectId = await stream.CompleteAsync().ConfigureAwait(false); + var part = new ObjectPartInfo(offset, uploaded, objectId); + offset += uploaded; + progressInfo.AddPart(part); + + remain -= bytesToWrite; + + if (isLastPart) + { + if (objectsCount == 1) + { + return progressInfo.GetPart(0).ObjectId; + } + + if (parentHeader == null) continue; + + // Once all parts of the object are uploaded, they must be linked into a single entity + var linkObject = new FrostFsLinkObject(header.ContainerId, progressInfo.SplitId, parentHeader, + [.. progressInfo.GetParts().Select(p => p.ObjectId)]); + + await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false); + + // Retrieve the ID of the linked object + resultObjectId = FrostFsObjectId.FromHash(prm.Header!.GetHeader().Split!.Parent.Value.Span); + return resultObjectId; + } + } + + throw new FrostFsException("Unexpected error: cannot send object"); + } + finally + { + if (isRentBuffer && buffer != null) + { + ArrayPool.Shared.Return(buffer); + } + } + } + + internal async Task PutClientCutSingleObjectAsync(PrmObjectClientCutPut args, CallContext ctx) + { + if (args.Payload == null) + throw new ArgumentException(nameof(args.Payload)); + + if (args.Header == null) + throw new ArgumentException(nameof(args.Header)); + + var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx).ConfigureAwait(false); + int partSize = (int)networkSettings.MaxObjectSize; + + int chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize; + + ulong fullLength; + + // Information about the uploaded parts. + var progressInfo = args.Progress; // + var offset = 0L; + + if (progressInfo != null && progressInfo.GetParts().Count > 0) + { + if (!args.Payload.CanSeek) + { + throw new FrostFsException("Cannot resume client cut upload for this stream. Seek must be supported."); + } + + var lastPart = progressInfo.GetLast(); + args.Payload.Position = lastPart.Offset + lastPart.Length; + fullLength = (ulong)(args.Payload.Length - args.Payload.Position); + offset = args.Payload.Position; + } + else + { + if (args.Header.PayloadLength > 0) + fullLength = args.Header.PayloadLength; + else if (args.Payload.CanSeek) + fullLength = (ulong)args.Payload.Length; + else + throw new ArgumentException("The stream does not have a length and payload length is not defined"); + } + + //define collection capacity + var restPart = (fullLength % (ulong)partSize) > 0 ? 1 : 0; + var objectsCount = fullLength > 0 ? (int)(fullLength / (ulong)partSize) + restPart : 0; // if the object fits one part, it can be loaded as non-complex object if (objectsCount == 1) @@ -418,60 +590,54 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl return singlePartResult.ObjectId; } - List parts = new(objectsCount); + progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); - SplitId splitId = new(); + var remain = fullLength; - // keep attributes for the large object - var attributes = args.Header!.Attributes.ToArray(); - header.Attributes = null; - - var remain = args.PutObjectContext.FullLength; - - FrostFsObjectHeader? parentHeader = null; - - var lastIndex = objectsCount - 1; - - bool rentBuffer = false; byte[]? buffer = null; + bool isRentBuffer = false; try { - for (int i = 0; i < objectsCount; i++) + if (args.CustomBuffer != null) { - if (args.CustomBuffer != null) + if (args.CustomBuffer.Length < partSize) { - if (args.CustomBuffer.Length < partSize) - { - throw new ArgumentException($"Buffer size is too small. A buffer with capacity {partSize} is required"); - } - - buffer = args.CustomBuffer; - } - else - { - buffer = ArrayPool.Shared.Rent(partSize); - rentBuffer = true; + throw new ArgumentException($"Buffer size is too small. A buffer with capacity {partSize} is required"); } + buffer = args.CustomBuffer; + } + else + { + buffer = ArrayPool.Shared.Rent(partSize); + isRentBuffer = true; + } + + FrostFsObjectHeader? parentHeader = null; + + for (int i = 0; i < objectsCount;) + { + i++; var bytesToWrite = Math.Min((ulong)partSize, remain); - var size = await stream.ReadAsync(buffer, 0, (int)bytesToWrite).ConfigureAwait(false); + var size = await args.Payload.ReadAsync(buffer, 0, (int)bytesToWrite).ConfigureAwait(false); - if (i == lastIndex) + if (i == objectsCount) { - parentHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, attributes) + parentHeader = new FrostFsObjectHeader(args.Header.ContainerId, FrostFsObjectType.Regular) { - PayloadLength = args.PutObjectContext.FullLength + PayloadLength = args.PutObjectContext.FullLength, + Attributes = args.Header.Attributes }; } // Uploading the next part of the object. Note: the request must contain a non-null SplitId parameter var partHeader = new FrostFsObjectHeader( - header.ContainerId, + args.Header.ContainerId, FrostFsObjectType.Regular, [], - new FrostFsSplit(splitId, parts.LastOrDefault(), + new FrostFsSplit(progressInfo.SplitId, progressInfo.GetLast().ObjectId, parentHeader: parentHeader)) { PayloadLength = (ulong)size @@ -484,15 +650,18 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var prm = new PrmSingleObjectPut(obj); - var objId = await PutSingleObjectAsync(prm, ctx).ConfigureAwait(false); + var objectId = await PutSingleObjectAsync(prm, ctx).ConfigureAwait(false); - parts.Add(objId); + var part = new ObjectPartInfo(offset, size, objectId); + progressInfo.AddPart(part); - if (i < lastIndex) + offset += size; + + if (i < objectsCount) continue; // Once all parts of the object are uploaded, they must be linked into a single entity - var linkObject = new FrostFsLinkObject(header.ContainerId, splitId, parentHeader!, parts); + var linkObject = new FrostFsLinkObject(args.Header.ContainerId, progressInfo.SplitId, parentHeader!, [.. progressInfo.GetParts().Select(p => p.ObjectId)]); _ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false); @@ -504,7 +673,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl } finally { - if (rentBuffer && buffer != null) + if (isRentBuffer && buffer != null) { ArrayPool.Shared.Return(buffer); } From 764b669295bbf1ddf55c65693029180cf8a23bac Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 16 Apr 2025 13:50:18 +0300 Subject: [PATCH 61/65] [#63] Set System.Text.Json 7.0.1 as a reference Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 4 ++-- src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj | 4 ++-- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index ed050a2..f8aefb7 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -6,7 +6,7 @@ enable AllEnabledByDefault FrostFS.SDK.Client - 1.0.4 + 1.0.5 C# SDK for FrostFS gRPC native protocol @@ -45,7 +45,7 @@ - + diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 43f7187..6a400c2 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Cryptography - 1.0.4 + 1.0.5 Cryptography tools for C# SDK @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 72bfe4c..81a8490 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Protos - 1.0.4 + 1.0.5 Protobuf client for C# SDK From f099edb17b9089099d87d8733a5164c715d13a17 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 17 Apr 2025 10:07:32 +0300 Subject: [PATCH 62/65] [#64] Fix for client cut logic Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/AssemblyInfo.cs | 4 ++-- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 2 +- src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs | 7 ++++--- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 4 ++-- .../FrostFS.SDK.Cryptography.csproj | 2 +- src/FrostFS.SDK.Protos/AssemblyInfo.cs | 4 ++-- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/FrostFS.SDK.Client/AssemblyInfo.cs b/src/FrostFS.SDK.Client/AssemblyInfo.cs index 8703376..2c89c60 100644 --- a/src/FrostFS.SDK.Client/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Client/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: AssemblyCompany("FrostFS.SDK.Client")] -[assembly: AssemblyFileVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Client")] [assembly: AssemblyTitle("FrostFS.SDK.Client")] -[assembly: AssemblyVersion("1.0.4")] +[assembly: AssemblyVersion("1.0.6")] diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index f8aefb7..6fb9ae4 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -6,7 +6,7 @@ enable AllEnabledByDefault FrostFS.SDK.Client - 1.0.5 + 1.0.6 C# SDK for FrostFS gRPC native protocol diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index ed9e9bc..a8b748f 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -582,14 +582,15 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var restPart = (fullLength % (ulong)partSize) > 0 ? 1 : 0; var objectsCount = fullLength > 0 ? (int)(fullLength / (ulong)partSize) + restPart : 0; - // if the object fits one part, it can be loaded as non-complex object - if (objectsCount == 1) + // if the object fits one part, it can be loaded as non-complex object, but if it is not upload resuming + if (objectsCount == 1 && progressInfo != null && progressInfo.GetLast().Length == 0) { args.PutObjectContext.MaxObjectSizeCache = partSize; + args.PutObjectContext.FullLength = fullLength; var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); return singlePartResult.ObjectId; } - + progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); var remain = fullLength; diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index 9f33f25..ba08d91 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: AssemblyCompany("FrostFS.SDK.Cryptography")] -[assembly: AssemblyFileVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] -[assembly: AssemblyVersion("1.0.4.0")] +[assembly: AssemblyVersion("1.0.6.0")] diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 6a400c2..0bb72c5 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Cryptography - 1.0.5 + 1.0.6 Cryptography tools for C# SDK diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index c3c813c..27da7e7 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: AssemblyCompany("FrostFS.SDK.Protos")] -[assembly: AssemblyFileVersion("1.0.4.0")] +[assembly: AssemblyFileVersion("1.0.6.0")] [assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] -[assembly: AssemblyVersion("1.0.4.0")] +[assembly: AssemblyVersion("1.0.6.0")] diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index 81a8490..e165524 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Protos - 1.0.5 + 1.0.6 Protobuf client for C# SDK From 20586d8adaa44810ca6478d3538477b33af38e6f Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Thu, 17 Apr 2025 13:41:41 +0300 Subject: [PATCH 63/65] [#64] Add string naming Signed-off-by: Pavel Gross --- keyfile.snk | Bin 0 -> 596 bytes src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs | 3 --- src/FrostFS.SDK.Client/AssemblyInfo.cs | 10 ++++++++-- src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj | 2 ++ src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 3 +-- .../FrostFS.SDK.Cryptography.csproj | 2 ++ src/FrostFS.SDK.Protos/AssemblyInfo.cs | 3 +-- src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj | 8 +++++--- src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj | 10 +++++++--- 9 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 keyfile.snk diff --git a/keyfile.snk b/keyfile.snk new file mode 100644 index 0000000000000000000000000000000000000000..a14537bebfd9a433f5892391ede8ca82350481cb GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097rxsv-7>(5Jtw6UYF7?&vwQvZda1_NA2 z+*Sn3s#vZufc43^Qa|op(Yv0h&*56Khvp-knAP8@78SjBNL^AoIzMHk|>H~JiY-Y@%qoT^WXX&U3Z>e*Iks*+~|Km98<14q{T*PUo+~c7N}64uBG@1KxX*&RkS|O@aG)X+MAv2 z4LklzkL$6|$Qyhp)nCZFHM$b$kHGjJ+#ckXJU0Mh?E;0C)K`IkNKj~u3_#`y#$MHy zV6+UvVgNo#&vw!SS4*ZmrR2GW{Dcl2I_(tMA zRB$RGHap$PL5K8Id4UNFX{eLSZKZ!(T9`AP!&j7!ebndcojP;XT*h5-& iO%&N(%fUyp&bxLGnhVRV%2K@Dw@=W$$KvKDWo>MpA|Ues literal 0 HcmV?d00001 diff --git a/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs b/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs index 738eb54..24f784e 100644 --- a/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs +++ b/src/FrostFS.SDK.Client/ApeRules/RuleSerializer.cs @@ -1,7 +1,4 @@ using System; -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("FrostFS.SDK.Tests")] namespace FrostFS.SDK.Client; diff --git a/src/FrostFS.SDK.Client/AssemblyInfo.cs b/src/FrostFS.SDK.Client/AssemblyInfo.cs index 2c89c60..4d87f8c 100644 --- a/src/FrostFS.SDK.Client/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Client/AssemblyInfo.cs @@ -1,8 +1,14 @@ using System.Reflection; +using System.Runtime.CompilerServices; -[assembly: AssemblyCompany("FrostFS.SDK.Client")] +[assembly: AssemblyCompany("TrueCloudLab")] +[assembly: InternalsVisibleTo("FrostFS.SDK.Tests, PublicKey=" + + "002400000480000094000000060200000024000052534131000400000100010089b992fb14ebcf"+ + "4b85b4b1a3af1897290c52ff85a106035c47dc5604cbaa58ae3180f5c9b8523fee5dd1bb9ea9cf"+ + "e15ab287e6239c98d5dfa91615bd77485d523a3a3f65a4e5028454cedd5ac4d9eca6da18b81985"+ + "ac6905d33cc64b5a2587050c16f67b71ef8889dbd3c90ef7cc0b06bbbe09886601d195f5db179a"+ + "3c2a25b1")] [assembly: AssemblyFileVersion("1.0.6.0")] -[assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Client")] [assembly: AssemblyTitle("FrostFS.SDK.Client")] [assembly: AssemblyVersion("1.0.6")] diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 6fb9ae4..037a2f4 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -32,6 +32,8 @@ false True + True + .\\..\\..\\keyfile.snk diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index ba08d91..cff5cc1 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,8 +1,7 @@ using System.Reflection; -[assembly: AssemblyCompany("FrostFS.SDK.Cryptography")] +[assembly: AssemblyCompany("TrueCloudLab")] [assembly: AssemblyFileVersion("1.0.6.0")] -[assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] [assembly: AssemblyVersion("1.0.6.0")] diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 0bb72c5..20e0a83 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -26,6 +26,8 @@ false + True + .\\..\\..\\keyfile.snk diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index 27da7e7..f16be42 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -1,8 +1,7 @@ using System.Reflection; -[assembly: AssemblyCompany("FrostFS.SDK.Protos")] +[assembly: AssemblyCompany("TrueCloudLab")] [assembly: AssemblyFileVersion("1.0.6.0")] -[assembly: AssemblyInformationalVersion("1.0.0+d6fe0344538a223303c9295452f0ad73681ca376")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] [assembly: AssemblyVersion("1.0.6.0")] diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index e165524..b6bc876 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -13,19 +13,21 @@ - true + true - <_SkipUpgradeNetAnalyzersNuGetWarning>true + <_SkipUpgradeNetAnalyzersNuGetWarning>true - true + true false + True + .\\..\\..\\keyfile.snk diff --git a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj index 30c0564..37bbace 100644 --- a/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj +++ b/src/FrostFS.SDK.Tests/FrostFS.SDK.Tests.csproj @@ -10,11 +10,16 @@ - true + true - true + true + + + + True + .\\..\\..\\keyfile.snk @@ -42,5 +47,4 @@ PreserveNewest - From eebba7665b77a059a593ba98a403242d111c2cbc Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 23 Apr 2025 00:30:34 +0300 Subject: [PATCH 64/65] [#67] Add unit tests Signed-off-by: Pavel Gross --- .../Models/Object/UploadInfo.cs | 2 - .../Models/Response/FrostFsResponseStatus.cs | 1 + src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs | 9 +- src/FrostFS.SDK.Tests/Unit/ContainerTest.cs | 22 ++ src/FrostFS.SDK.Tests/Unit/MetaheaderTests.cs | 29 ++ .../Unit/NetmapSnapshotTests.cs | 101 +++++ .../Unit/NetworkSettingsTests.cs | 68 ++++ src/FrostFS.SDK.Tests/Unit/NetworkTest.cs | 225 ----------- src/FrostFS.SDK.Tests/Unit/NodeInfoTests.cs | 69 ++++ src/FrostFS.SDK.Tests/Unit/ObjectTest.cs | 356 +++++++++++++++++- .../Unit/ObjectToolsTests.cs | 99 +++++ .../Unit/PlacementPolicyTests.cs | 307 +++++++++++++++ src/FrostFS.SDK.Tests/Unit/SignatureTests.cs | 39 ++ 13 files changed, 1094 insertions(+), 233 deletions(-) create mode 100644 src/FrostFS.SDK.Tests/Unit/MetaheaderTests.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/NetmapSnapshotTests.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/NetworkSettingsTests.cs delete mode 100644 src/FrostFS.SDK.Tests/Unit/NetworkTest.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/NodeInfoTests.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/ObjectToolsTests.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs create mode 100644 src/FrostFS.SDK.Tests/Unit/SignatureTests.cs diff --git a/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs b/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs index 748fdfe..ca96c7d 100644 --- a/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs +++ b/src/FrostFS.SDK.Client/Models/Object/UploadInfo.cs @@ -1,5 +1,3 @@ -using FrostFS.SDK; - namespace FrostFS.SDK.Client; public readonly struct ObjectPartInfo(long offset, int length, FrostFsObjectId objectId) : System.IEquatable diff --git a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs index 9e1a846..7b003e7 100644 --- a/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs +++ b/src/FrostFS.SDK.Client/Models/Response/FrostFsResponseStatus.cs @@ -3,6 +3,7 @@ namespace FrostFS.SDK; 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; diff --git a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs index f33b9a1..772f92e 100644 --- a/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs +++ b/src/FrostFS.SDK.Tests/Mocks/ObjectMock.cs @@ -41,6 +41,8 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) public Collection RangeHashResponses { get; } = []; + public Action? Callback; + public override Mock GetMock() { var mock = new Mock(); @@ -165,9 +167,14 @@ public class ObjectMocker(string key) : ObjectServiceBase(key) It.IsAny())) .Returns((PutSingleRequest r, Metadata m, DateTime? dt, CancellationToken ct) => { + Callback?.Invoke(); Verifier.CheckRequest(r); - PutSingleRequests.Add(r); + var req = r.Clone(); + + // Clone method does not clone the payload but keeps a reference + req.Body.Object.Payload = ByteString.CopyFrom(r.Body.Object.Payload.ToByteArray()); + PutSingleRequests.Add(req); return new AsyncUnaryCall( Task.FromResult(putSingleResponse), diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs index cf4bdfa..e713b4a 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using FrostFS.Netmap; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; using FrostFS.SDK.Cryptography; @@ -10,6 +11,27 @@ namespace FrostFS.SDK.Tests.Unit; [SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] public class ContainerTest : ContainerTestsBase { + [Theory] + [InlineData(1, "test", 0, 0)] + + public void ReplicaToMessagelTest(int count, string selector, uint ecDataCount, uint ecParityCount) + { + FrostFsReplica replica = new() + { + Count = count, + Selector = selector, + EcDataCount = ecDataCount, + EcParityCount = ecParityCount + }; + + Replica message = replica.ToMessage(); + + Assert.Equal((uint)count, message.Count); + Assert.Equal(selector, message.Selector); + Assert.Equal(ecDataCount, message.EcDataCount); + Assert.Equal(ecParityCount, message.EcParityCount); + } + [Fact] public async void CreateContainerTest() { diff --git a/src/FrostFS.SDK.Tests/Unit/MetaheaderTests.cs b/src/FrostFS.SDK.Tests/Unit/MetaheaderTests.cs new file mode 100644 index 0000000..3a71d78 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/MetaheaderTests.cs @@ -0,0 +1,29 @@ +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; + +namespace FrostFS.SDK.Tests.Unit; + +public class MetaheaderTests +{ + [Theory] + [InlineData(2, 13, 1, 1)] + [InlineData(200, 0, 1000000, 8)] + public void MetaheaderTest(int major, int minor, int epoch, int ttl) + { + MetaHeader metaHeader = new MetaHeader( + new FrostFsVersion( + major: 2, + minor: 13 + ), + epoch: 0, + ttl: 2 + ); + + var result = metaHeader.ToMessage(); + + Assert.Equal((ulong)metaHeader.Epoch, result.Epoch); + Assert.Equal((ulong)metaHeader.Ttl, result.Ttl); + Assert.Equal((ulong)metaHeader.Version.Major, result.Version.Major); + Assert.Equal((ulong)metaHeader.Version.Minor, result.Version.Minor); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/NetmapSnapshotTests.cs b/src/FrostFS.SDK.Tests/Unit/NetmapSnapshotTests.cs new file mode 100644 index 0000000..233e12e --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/NetmapSnapshotTests.cs @@ -0,0 +1,101 @@ +using FrostFS.Netmap; +using FrostFS.SDK.Client; + +using Google.Protobuf; + +namespace FrostFS.SDK.Tests.Unit; + +public class NetmapSnapshotTests : NetworkTestsBase +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + + public async void NetmapSnapshotTest(bool useContext) + { + var body = new NetmapSnapshotResponse.Types.Body + { + Netmap = new Netmap.Netmap { Epoch = 99 } + }; + + var nodeInfo1 = new NodeInfo + { + State = NodeInfo.Types.State.Online, + PublicKey = ByteString.CopyFrom([1, 2, 3]) + }; + + nodeInfo1.Addresses.Add("address1"); + nodeInfo1.Addresses.Add("address2"); + nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); + nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); + + var nodeInfo2 = new NodeInfo + { + State = NodeInfo.Types.State.Offline, + PublicKey = ByteString.CopyFrom([3, 4, 5]) + }; + + nodeInfo2.Addresses.Add("address3"); + nodeInfo2.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key3", Value = "value3" }); + + body.Netmap.Nodes.Add(nodeInfo1); + body.Netmap.Nodes.Add(nodeInfo2); + + Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; + + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient(DefaultSettings).GetNetmapSnapshotAsync(ctx); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(99u, result.Epoch); + Assert.Equal(2, result.NodeInfoCollection.Count); + + var node1 = result.NodeInfoCollection[0]; + Assert.Equal(NodeState.Online, node1.State); + Assert.Equal(2, node1.Addresses.Count); + Assert.Equal("address1", node1.Addresses.ElementAt(0)); + Assert.Equal("address2", node1.Addresses.ElementAt(1)); + + Assert.Equal(2, node1.Attributes.Count); + + Assert.Equal("key1", node1.Attributes.ElementAt(0).Key); + Assert.Equal("value1", node1.Attributes.ElementAt(0).Value); + Assert.Equal("key2", node1.Attributes.ElementAt(1).Key); + Assert.Equal("value2", node1.Attributes.ElementAt(1).Value); + + var node2 = result.NodeInfoCollection[1]; + Assert.Equal(NodeState.Offline, node2.State); + Assert.Single(node2.Addresses); + Assert.Equal("address3", node2.Addresses.ElementAt(0)); + + Assert.Single(node2.Attributes); + + Assert.Equal("key3", node2.Attributes.ElementAt(0).Key); + Assert.Equal("value3", node2.Attributes.ElementAt(0).Value); + + if (useContext) + { + Assert.NotNull(Mocker.NetmapSnapshotRequest); + Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); + + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.NotNull(Mocker.NetmapSnapshotRequest); + Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/NetworkSettingsTests.cs b/src/FrostFS.SDK.Tests/Unit/NetworkSettingsTests.cs new file mode 100644 index 0000000..3f3fb2f --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/NetworkSettingsTests.cs @@ -0,0 +1,68 @@ +using System.Diagnostics.CodeAnalysis; +using FrostFS.SDK.Client; + +namespace FrostFS.SDK.Tests.Unit; + +[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] +public class NetworkSettingsTests : NetworkTestsBase +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NetworkSettingsTest(bool useContext) + { + Mocker.Parameters.Add("AuditFee", [1]); + Mocker.Parameters.Add("BasicIncomeRate", [2]); + Mocker.Parameters.Add("ContainerFee", [3]); + Mocker.Parameters.Add("ContainerAliasFee", [4]); + Mocker.Parameters.Add("EpochDuration", [5]); + Mocker.Parameters.Add("InnerRingCandidateFee", [6]); + Mocker.Parameters.Add("MaxECDataCount", [7]); + Mocker.Parameters.Add("MaxECParityCount", [8]); + Mocker.Parameters.Add("MaxObjectSize", [9]); + Mocker.Parameters.Add("WithdrawFee", [10]); + Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]); + Mocker.Parameters.Add("MaintenanceModeAllowed", [1]); + + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient(DefaultSettings).GetNetworkSettingsAsync(ctx); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(Mocker.Parameters["AuditFee"], [(byte)result.AuditFee]); + Assert.Equal(Mocker.Parameters["BasicIncomeRate"], [(byte)result.BasicIncomeRate]); + Assert.Equal(Mocker.Parameters["ContainerFee"], [(byte)result.ContainerFee]); + Assert.Equal(Mocker.Parameters["ContainerAliasFee"], [(byte)result.ContainerAliasFee]); + Assert.Equal(Mocker.Parameters["EpochDuration"], [(byte)result.EpochDuration]); + Assert.Equal(Mocker.Parameters["InnerRingCandidateFee"], [(byte)result.InnerRingCandidateFee]); + Assert.Equal(Mocker.Parameters["MaxECDataCount"], [(byte)result.MaxECDataCount]); + Assert.Equal(Mocker.Parameters["MaxECParityCount"], [(byte)result.MaxECParityCount]); + Assert.Equal(Mocker.Parameters["MaxObjectSize"], [(byte)result.MaxObjectSize]); + Assert.Equal(Mocker.Parameters["WithdrawFee"], [(byte)result.WithdrawFee]); + + Assert.True(result.HomomorphicHashingDisabled); + Assert.True(result.MaintenanceModeAllowed); + + if (useContext) + { + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.NotNull(Mocker.NetworkInfoRequest); + Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } +} diff --git a/src/FrostFS.SDK.Tests/Unit/NetworkTest.cs b/src/FrostFS.SDK.Tests/Unit/NetworkTest.cs deleted file mode 100644 index b1146f7..0000000 --- a/src/FrostFS.SDK.Tests/Unit/NetworkTest.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using FrostFS.Netmap; -using FrostFS.SDK.Client; - -using Google.Protobuf; - -namespace FrostFS.SDK.Tests.Unit; - -[SuppressMessage("Reliability", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "Default Value is correct for tests")] -public class NetworkTest : NetworkTestsBase -{ - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void NetworkSettingsTest(bool useContext) - { - Mocker.Parameters.Add("AuditFee", [1]); - Mocker.Parameters.Add("BasicIncomeRate", [2]); - Mocker.Parameters.Add("ContainerFee", [3]); - Mocker.Parameters.Add("ContainerAliasFee", [4]); - Mocker.Parameters.Add("EpochDuration", [5]); - Mocker.Parameters.Add("InnerRingCandidateFee", [6]); - Mocker.Parameters.Add("MaxECDataCount", [7]); - Mocker.Parameters.Add("MaxECParityCount", [8]); - Mocker.Parameters.Add("MaxObjectSize", [9]); - Mocker.Parameters.Add("WithdrawFee", [10]); - Mocker.Parameters.Add("HomomorphicHashingDisabled", [1]); - Mocker.Parameters.Add("MaintenanceModeAllowed", [1]); - - var ctx = useContext - ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) - : default; - - var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - - var result = await GetClient(DefaultSettings).GetNetworkSettingsAsync(ctx); - - var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); - - Assert.NotNull(result); - - Assert.Equal(Mocker.Parameters["AuditFee"], [(byte)result.AuditFee]); - Assert.Equal(Mocker.Parameters["BasicIncomeRate"], [(byte)result.BasicIncomeRate]); - Assert.Equal(Mocker.Parameters["ContainerFee"], [(byte)result.ContainerFee]); - Assert.Equal(Mocker.Parameters["ContainerAliasFee"], [(byte)result.ContainerAliasFee]); - Assert.Equal(Mocker.Parameters["EpochDuration"], [(byte)result.EpochDuration]); - Assert.Equal(Mocker.Parameters["InnerRingCandidateFee"], [(byte)result.InnerRingCandidateFee]); - Assert.Equal(Mocker.Parameters["MaxECDataCount"], [(byte)result.MaxECDataCount]); - Assert.Equal(Mocker.Parameters["MaxECParityCount"], [(byte)result.MaxECParityCount]); - Assert.Equal(Mocker.Parameters["MaxObjectSize"], [(byte)result.MaxObjectSize]); - Assert.Equal(Mocker.Parameters["WithdrawFee"], [(byte)result.WithdrawFee]); - - Assert.True(result.HomomorphicHashingDisabled); - Assert.True(result.MaintenanceModeAllowed); - - if (useContext) - { - Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); - Assert.NotNull(Mocker.DateTime); - - Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); - Assert.True(Mocker.DateTime.Value <= validTimeoutTo); - } - else - { - Assert.NotNull(Mocker.NetworkInfoRequest); - Assert.Empty(Mocker.NetworkInfoRequest.MetaHeader.XHeaders); - Assert.Null(Mocker.DateTime); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - - public async void NetmapSnapshotTest(bool useContext) - { - var body = new NetmapSnapshotResponse.Types.Body - { - Netmap = new Netmap.Netmap { Epoch = 99 } - }; - - var nodeInfo1 = new NodeInfo - { - State = NodeInfo.Types.State.Online, - PublicKey = ByteString.CopyFrom([1, 2, 3]) - }; - - nodeInfo1.Addresses.Add("address1"); - nodeInfo1.Addresses.Add("address2"); - nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); - nodeInfo1.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); - - var nodeInfo2 = new NodeInfo - { - State = NodeInfo.Types.State.Offline, - PublicKey = ByteString.CopyFrom([3, 4, 5]) - }; - - nodeInfo2.Addresses.Add("address3"); - nodeInfo2.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key3", Value = "value3" }); - - body.Netmap.Nodes.Add(nodeInfo1); - body.Netmap.Nodes.Add(nodeInfo2); - - Mocker.NetmapSnapshotResponse = new NetmapSnapshotResponse { Body = body }; - - var ctx = useContext - ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) - : default; - - var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - - var result = await GetClient(DefaultSettings).GetNetmapSnapshotAsync(ctx); - - var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); - - Assert.NotNull(result); - - Assert.Equal(99u, result.Epoch); - Assert.Equal(2, result.NodeInfoCollection.Count); - - var node1 = result.NodeInfoCollection[0]; - Assert.Equal(NodeState.Online, node1.State); - Assert.Equal(2, node1.Addresses.Count); - Assert.Equal("address1", node1.Addresses.ElementAt(0)); - Assert.Equal("address2", node1.Addresses.ElementAt(1)); - - Assert.Equal(2, node1.Attributes.Count); - - Assert.Equal("key1", node1.Attributes.ElementAt(0).Key); - Assert.Equal("value1", node1.Attributes.ElementAt(0).Value); - Assert.Equal("key2", node1.Attributes.ElementAt(1).Key); - Assert.Equal("value2", node1.Attributes.ElementAt(1).Value); - - var node2 = result.NodeInfoCollection[1]; - Assert.Equal(NodeState.Offline, node2.State); - Assert.Single(node2.Addresses); - Assert.Equal("address3", node2.Addresses.ElementAt(0)); - - Assert.Single(node2.Attributes); - - Assert.Equal("key3", node2.Attributes.ElementAt(0).Key); - Assert.Equal("value3", node2.Attributes.ElementAt(0).Value); - - if (useContext) - { - Assert.NotNull(Mocker.NetmapSnapshotRequest); - Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); - - Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); - Assert.NotNull(Mocker.DateTime); - Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); - Assert.True(Mocker.DateTime.Value <= validTimeoutTo); - } - else - { - Assert.NotNull(Mocker.NetmapSnapshotRequest); - Assert.Empty(Mocker.NetmapSnapshotRequest.MetaHeader.XHeaders); - Assert.Null(Mocker.DateTime); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public async void NodeInfoTest(bool useContext) - { - var body = new LocalNodeInfoResponse.Types.Body - { - NodeInfo = new NodeInfo() - { - State = NodeInfo.Types.State.Online, - PublicKey = ByteString.CopyFrom([1, 2, 3]) - }, - Version = new Refs.Version { Major = 2, Minor = 12 } - }; - - body.NodeInfo.Addresses.Add("address1"); - body.NodeInfo.Addresses.Add("address2"); - body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); - body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); - - Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; - - var ctx = useContext - ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) - : default; - - var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); - - var result = await GetClient(DefaultSettings).GetNodeInfoAsync(ctx); - - var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); - - Assert.NotNull(result); - - Assert.Equal(NodeState.Online, result.State); - - Assert.Equal(2, result.Addresses.Count); - Assert.Equal("address1", result.Addresses.ElementAt(0)); - Assert.Equal("address2", result.Addresses.ElementAt(1)); - - Assert.Equal(2, result.Attributes.Count); - Assert.Equal("value1", result.Attributes["key1"]); - Assert.Equal("value2", result.Attributes["key2"]); - - Assert.NotNull(Mocker.LocalNodeInfoRequest); - if (useContext) - { - Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); - Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); - Assert.NotNull(Mocker.DateTime); - - Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); - Assert.True(Mocker.DateTime.Value <= validTimeoutTo); - } - else - { - Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); - Assert.Null(Mocker.DateTime); - } - } -} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/NodeInfoTests.cs b/src/FrostFS.SDK.Tests/Unit/NodeInfoTests.cs new file mode 100644 index 0000000..6449beb --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/NodeInfoTests.cs @@ -0,0 +1,69 @@ +using FrostFS.Netmap; +using FrostFS.SDK.Client; +using Google.Protobuf; + +namespace FrostFS.SDK.Tests.Unit; + +public class NodeInfoTests : NetworkTestsBase +{ + [Theory] + [InlineData(false)] + [InlineData(true)] + public async void NodeInfoTest(bool useContext) + { + var body = new LocalNodeInfoResponse.Types.Body + { + NodeInfo = new NodeInfo() + { + State = NodeInfo.Types.State.Online, + PublicKey = ByteString.CopyFrom([1, 2, 3]) + }, + Version = new Refs.Version { Major = 2, Minor = 12 } + }; + + body.NodeInfo.Addresses.Add("address1"); + body.NodeInfo.Addresses.Add("address2"); + body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key1", Value = "value1" }); + body.NodeInfo.Attributes.Add(new NodeInfo.Types.Attribute { Key = "key2", Value = "value2" }); + + Mocker.NodeInfoResponse = new LocalNodeInfoResponse { Body = body }; + + var ctx = useContext + ? new CallContext(TimeSpan.FromSeconds(20), Mocker.CancellationTokenSource.Token) + : default; + + var validTimeoutFrom = DateTime.UtcNow.AddSeconds(20); + + var result = await GetClient(DefaultSettings).GetNodeInfoAsync(ctx); + + var validTimeoutTo = DateTime.UtcNow.AddSeconds(20); + + Assert.NotNull(result); + + Assert.Equal(NodeState.Online, result.State); + + Assert.Equal(2, result.Addresses.Count); + Assert.Equal("address1", result.Addresses.ElementAt(0)); + Assert.Equal("address2", result.Addresses.ElementAt(1)); + + Assert.Equal(2, result.Attributes.Count); + Assert.Equal("value1", result.Attributes["key1"]); + Assert.Equal("value2", result.Attributes["key2"]); + + Assert.NotNull(Mocker.LocalNodeInfoRequest); + if (useContext) + { + Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); + Assert.Equal(Mocker.CancellationTokenSource.Token, Mocker.CancellationToken); + Assert.NotNull(Mocker.DateTime); + + Assert.True(Mocker.DateTime.Value >= validTimeoutFrom); + Assert.True(Mocker.DateTime.Value <= validTimeoutTo); + } + else + { + Assert.Empty(Mocker.LocalNodeInfoRequest.MetaHeader.XHeaders); + Assert.Null(Mocker.DateTime); + } + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs index 7a595e6..985f02e 100644 --- a/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ObjectTest.cs @@ -6,8 +6,9 @@ using System.Text; using FrostFS.Refs; using FrostFS.SDK.Client; using FrostFS.SDK.Client.Mappers.GRPC; - +using FrostFS.SDK.Cryptography; using Google.Protobuf; +using Org.BouncyCastle.Utilities; namespace FrostFS.SDK.Tests.Unit; @@ -60,7 +61,7 @@ public class ObjectTest : ObjectTestsBase var param = new PrmObjectClientCutPut( Mocker.ObjectHeader, payload: new MemoryStream(bytes), - bufferMaxSize: 1024); + bufferMaxSize: blockSize); Random rnd = new(); @@ -87,7 +88,7 @@ public class ObjectTest : ObjectTestsBase // PART1 Assert.Equal(blockSize, objects[0].Payload.Length); - Assert.True(bytes.AsMemory(0, blockSize).ToArray().SequenceEqual(objects[0].Payload)); + Assert.Equal(bytes.AsMemory(0, blockSize).ToArray(), objects[0].Payload); Assert.NotNull(objects[0].Header.Split.SplitId); Assert.Null(objects[0].Header.Split.Previous); @@ -96,7 +97,7 @@ public class ObjectTest : ObjectTestsBase // PART2 Assert.Equal(blockSize, objects[1].Payload.Length); - Assert.True(bytes.AsMemory(blockSize, blockSize).ToArray().SequenceEqual(objects[1].Payload)); + Assert.Equal(bytes.AsMemory(blockSize, blockSize).ToArray(), objects[1].Payload); Assert.Equal(objects[0].Header.Split.SplitId, objects[1].Header.Split.SplitId); Assert.True(objects[1].Header.Attributes.Count == 0); @@ -104,7 +105,7 @@ public class ObjectTest : ObjectTestsBase // last part Assert.Equal(bytes.Length % blockSize, objects[2].Payload.Length); - Assert.True(bytes.AsMemory(2 * blockSize).ToArray().SequenceEqual(objects[2].Payload)); + Assert.Equal(bytes.AsMemory(2 * blockSize).ToArray(), objects[2].Payload); Assert.NotNull(objects[3].Header.Split.Parent); Assert.NotNull(objects[3].Header.Split.ParentHeader); @@ -128,6 +129,351 @@ public class ObjectTest : ObjectTestsBase Assert.Equal(result.Value, modelObjId.ToString()); } + [Fact] + public async void ClientCutWithInterruptionOnFirstPartTest() + { + NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]); + + var blockSize = 2560; + byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); + var fileLength = bytes.Length; + + var splitId = Guid.NewGuid(); + var progress = new UploadProgressInfo(splitId); + + var param = new PrmObjectClientCutPut( + Mocker.ObjectHeader, + payload: new MemoryStream(bytes), + bufferMaxSize: blockSize, + progress: progress); + + Random rnd = new(); + + Collection objIds = new([new byte[32], new byte[32], new byte[32]]); + rnd.NextBytes(objIds.ElementAt(0)); + rnd.NextBytes(objIds.ElementAt(1)); + rnd.NextBytes(objIds.ElementAt(2)); + + foreach (var objId in objIds) + Mocker.ResultObjectIds!.Add(objId); + + int sentBlockCount = 0; + Mocker.Callback = () => + { + if (++sentBlockCount == 1) + throw new FrostFsException("some error"); + }; + + bool gotException = false; + try + { + var result = await GetClient().PutClientCutObjectAsync(param, default); + } + catch (FrostFsException ex) + { + if (ex.Message == "some error") + gotException = true; + } + + Assert.True(gotException); + + var singleObjects = Mocker.PutSingleRequests.ToArray(); + + Assert.Empty(singleObjects); + } + + [Fact] + public async void ClientCutWithInterruptionOnMiddlePartTest() + { + NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]); + + var blockSize = 2560; + byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); + var fileLength = bytes.Length; + + var splitId = Guid.Parse("67e4bbe9-86ca-474d-9385-6569ce89db61"); + var progress = new UploadProgressInfo(splitId); + + var param = new PrmObjectClientCutPut( + Mocker.ObjectHeader, + payload: new MemoryStream(bytes), + bufferMaxSize: blockSize, + progress: progress); + + Random rnd = new(); + + Collection objIds = new([new byte[32], new byte[32], new byte[32]]); + rnd.NextBytes(objIds.ElementAt(0)); + rnd.NextBytes(objIds.ElementAt(1)); + rnd.NextBytes(objIds.ElementAt(2)); + + foreach (var objId in objIds) + Mocker.ResultObjectIds!.Add(objId); + + int sentBlockCount = 0; + Mocker.Callback = () => + { + if (++sentBlockCount == 2) + throw new FrostFsException("some error"); + }; + + bool gotException = false; + try + { + _ = await GetClient().PutClientCutObjectAsync(param, default); + } + catch (FrostFsException ex) + { + if (ex.Message == "some error") + gotException = true; + } + + Assert.True(gotException); + + var singleObjects = Mocker.PutSingleRequests.ToArray(); + + Assert.NotNull(Mocker.ClientStreamWriter?.Messages); + + var objects = Mocker.PutSingleRequests.Select(o => o.Body.Object).ToArray(); + + Assert.Single(objects); + + Assert.Single(progress.GetParts()); + + var part = progress.GetPart(0); + Assert.Equal(0, part.Offset); + Assert.Equal(2560, part.Length); + + // PART1 + Assert.Equal(blockSize, objects[0].Payload.Length); + Assert.Equal(bytes.AsMemory(0, blockSize).ToArray(), objects[0].Payload); + + Assert.NotNull(objects[0].Header.Split.SplitId); + Assert.Null(objects[0].Header.Split.Previous); + Assert.True(objects[0].Header.Attributes.Count == 0); + Assert.Null(objects[0].Header.Split.Parent); + + // resume uploading + sentBlockCount = 10; + + var result = await GetClient().PutClientCutObjectAsync(param, default); + + singleObjects = Mocker.PutSingleRequests.ToArray(); + + Assert.NotNull(Mocker.ClientStreamWriter?.Messages); + + objects = Mocker.PutSingleRequests.Select(o => o.Body.Object).ToArray(); + + Assert.Equal(4, objects.Length); + + // PART1 + Assert.Equal(blockSize, objects[0].Payload.Length); + Assert.Equal(bytes.AsMemory(0, blockSize).ToArray(), objects[0].Payload); + + Assert.NotNull(objects[0].Header.Split.SplitId); + Assert.Null(objects[0].Header.Split.Previous); + Assert.True(objects[0].Header.Attributes.Count == 0); + Assert.Null(objects[0].Header.Split.Parent); + + // PART2 + Assert.Equal(blockSize, objects[1].Payload.Length); + Assert.Equal(bytes.AsMemory(blockSize, blockSize).ToArray(), objects[1].Payload); + + Assert.Equal(objects[0].Header.Split.SplitId, objects[1].Header.Split.SplitId); + Assert.True(objects[1].Header.Attributes.Count == 0); + Assert.Null(objects[1].Header.Split.Parent); + + // last part + Assert.Equal(bytes.Length % blockSize, objects[2].Payload.Length); + Assert.Equal(bytes.AsMemory(2 * blockSize).ToArray(), objects[2].Payload); + + Assert.NotNull(objects[3].Header.Split.Parent); + Assert.NotNull(objects[3].Header.Split.ParentHeader); + Assert.NotNull(objects[3].Header.Split.ParentSignature); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.True(objects[2].Header.Attributes.Count == 0); + + // link object + Assert.Equal(objects[2].Header.Split.Parent, objects[3].Header.Split.Parent); + Assert.Equal(objects[2].Header.Split.ParentHeader, objects[3].Header.Split.ParentHeader); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.Equal(0, (int)objects[3].Header.PayloadLength); + Assert.True(objects[3].Header.Attributes.Count == 0); + + Assert.Single(objects[3].Header.Split.ParentHeader.Attributes); + Assert.Equal("k", objects[3].Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", objects[3].Header.Split.ParentHeader.Attributes[0].Value); + + var modelObjId = FrostFsObjectId.FromHash(objects[3].Header.Split.Parent.Value.ToByteArray()); + + Assert.Equal(result.Value, modelObjId.ToString()); + + Assert.Equal(3, progress.GetParts().Count); + part = progress.GetPart(0); + Assert.Equal(0, part.Offset); + Assert.Equal(2560, part.Length); + + part = progress.GetPart(1); + Assert.Equal(2560, part.Offset); + Assert.Equal(2560, part.Length); + + part = progress.GetPart(2); + Assert.Equal(2 * 2560, part.Offset); + Assert.Equal(1620, part.Length); + } + + [Fact] + public async void ClientCutWithInterruptionOnLastPartTest() + { + NetworkMocker.Parameters.Add("MaxObjectSize", [0x0, 0xa]); + + var blockSize = 2560; + byte[] bytes = File.ReadAllBytes(@".\..\..\..\cat.jpg"); + var fileLength = bytes.Length; + + var splitId = Guid.Parse("67e4bbe9-86ca-474d-9385-6569ce89db61"); + var progress = new UploadProgressInfo(splitId); + + var param = new PrmObjectClientCutPut( + Mocker.ObjectHeader, + payload: new MemoryStream(bytes), + bufferMaxSize: blockSize, + progress: progress); + + Random rnd = new(); + + Collection objIds = new([new byte[32], new byte[32], new byte[32]]); + rnd.NextBytes(objIds.ElementAt(0)); + rnd.NextBytes(objIds.ElementAt(1)); + rnd.NextBytes(objIds.ElementAt(2)); + + foreach (var objId in objIds) + Mocker.ResultObjectIds!.Add(objId); + + int sentBlockCount = 0; + Mocker.Callback = () => + { + if (++sentBlockCount == 3) + throw new FrostFsException("some error"); + }; + + bool gotException = false; + try + { + _ = await GetClient().PutClientCutObjectAsync(param, default); + } + catch (FrostFsException ex) + { + if (ex.Message == "some error") + gotException = true; + } + + Assert.True(gotException); + + var singleObjects = Mocker.PutSingleRequests.ToArray(); + + Assert.NotNull(Mocker.ClientStreamWriter?.Messages); + + var objects = Mocker.PutSingleRequests.Select(o => o.Body.Object).ToArray(); + + Assert.Equal(2, objects.Length); + + Assert.Equal(2, progress.GetParts().Count); + + var part = progress.GetPart(0); + Assert.Equal(0, part.Offset); + Assert.Equal(2560, part.Length); + + part = progress.GetPart(1); + Assert.Equal(2560, part.Offset); + Assert.Equal(2560, part.Length); + + // PART1 + Assert.Equal(blockSize, objects[0].Payload.Length); + Assert.Equal(bytes.AsMemory(0, blockSize).ToArray(), objects[0].Payload); + + Assert.NotNull(objects[0].Header.Split.SplitId); + Assert.Null(objects[0].Header.Split.Previous); + Assert.True(objects[0].Header.Attributes.Count == 0); + Assert.Null(objects[0].Header.Split.Parent); + + // PART2 + Assert.Equal(blockSize, objects[1].Payload.Length); + Assert.Equal(bytes.AsMemory(blockSize, blockSize).ToArray(), objects[1].Payload); + + Assert.Equal(objects[0].Header.Split.SplitId, objects[1].Header.Split.SplitId); + Assert.True(objects[1].Header.Attributes.Count == 0); + Assert.Null(objects[1].Header.Split.Parent); + + // resume uploading + sentBlockCount = 10; + + var result = await GetClient().PutClientCutObjectAsync(param, default); + + singleObjects = Mocker.PutSingleRequests.ToArray(); + + Assert.NotNull(Mocker.ClientStreamWriter?.Messages); + + objects = Mocker.PutSingleRequests.Select(o => o.Body.Object).ToArray(); + + Assert.Equal(4, objects.Length); + + // PART1 + Assert.Equal(blockSize, objects[0].Payload.Length); + Assert.Equal(bytes.AsMemory(0, blockSize).ToArray(), objects[0].Payload); + + Assert.NotNull(objects[0].Header.Split.SplitId); + Assert.Null(objects[0].Header.Split.Previous); + Assert.True(objects[0].Header.Attributes.Count == 0); + Assert.Null(objects[0].Header.Split.Parent); + + // PART2 + Assert.Equal(blockSize, objects[1].Payload.Length); + Assert.Equal(bytes.AsMemory(blockSize, blockSize).ToArray(), objects[1].Payload); + + Assert.Equal(objects[0].Header.Split.SplitId, objects[1].Header.Split.SplitId); + Assert.True(objects[1].Header.Attributes.Count == 0); + Assert.Null(objects[1].Header.Split.Parent); + + // last part + Assert.Equal(bytes.Length % blockSize, objects[2].Payload.Length); + Assert.Equal(bytes.AsMemory(2 * blockSize).ToArray(), objects[2].Payload); + + Assert.NotNull(objects[3].Header.Split.Parent); + Assert.NotNull(objects[3].Header.Split.ParentHeader); + Assert.NotNull(objects[3].Header.Split.ParentSignature); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.True(objects[2].Header.Attributes.Count == 0); + + // link object + Assert.Equal(objects[2].Header.Split.Parent, objects[3].Header.Split.Parent); + Assert.Equal(objects[2].Header.Split.ParentHeader, objects[3].Header.Split.ParentHeader); + Assert.Equal(objects[2].Header.Split.SplitId, objects[3].Header.Split.SplitId); + Assert.Equal(0, (int)objects[3].Header.PayloadLength); + Assert.True(objects[3].Header.Attributes.Count == 0); + + Assert.Single(objects[3].Header.Split.ParentHeader.Attributes); + Assert.Equal("k", objects[3].Header.Split.ParentHeader.Attributes[0].Key); + Assert.Equal("v", objects[3].Header.Split.ParentHeader.Attributes[0].Value); + + var modelObjId = FrostFsObjectId.FromHash(objects[3].Header.Split.Parent.Value.ToByteArray()); + + Assert.Equal(result.Value, modelObjId.ToString()); + + Assert.Equal(3, progress.GetParts().Count); + part = progress.GetPart(0); + Assert.Equal(0, part.Offset); + Assert.Equal(2560, part.Length); + + part = progress.GetPart(1); + Assert.Equal(2560, part.Offset); + Assert.Equal(2560, part.Length); + + part = progress.GetPart(2); + Assert.Equal(2 * 2560, part.Offset); + Assert.Equal(1620, part.Length); + } + [Fact] public async void DeleteObject() { diff --git a/src/FrostFS.SDK.Tests/Unit/ObjectToolsTests.cs b/src/FrostFS.SDK.Tests/Unit/ObjectToolsTests.cs new file mode 100644 index 0000000..3c0245b --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/ObjectToolsTests.cs @@ -0,0 +1,99 @@ +using System.Security.Cryptography; +using System.Text; +using FrostFS.SDK.Client; +using FrostFS.SDK.Cryptography; + +namespace FrostFS.SDK.Tests.Unit; + +public class ObjectToolsTests +{ + internal readonly string key = "KwHDAJ66o8FoLBjVbjP2sWBmgBMGjt7Vv4boA7xQrBoAYBE397Aq"; + + [Fact] + public void CalculateObjectIdTest() + { + var payload = Encoding.UTF8.GetBytes("testPayload"); + + var payloadHash = SHA256.HashData(payload); + + FrostFsContainerId containerId = new("test"); + FrostFsObjectHeader header = new(containerId); + + var ecdsaKey = key.LoadWif(); + var owner = FrostFsOwner.FromKey(ecdsaKey); + + var clientKey = new ClientKey(ecdsaKey); + + var objId = ObjectTools.CalculateObjectId(header, payloadHash, owner, new FrostFsVersion(2, 13), clientKey); + + Assert.NotNull(objId.Value); + Assert.Equal("HuAojwCYi62iUKr1FtSCCkMLLWv1uAnznF8iSb1bRV1N", objId.Value); + } + + [Fact] + public void CalculateObjectIdTest1() + { + var payload = Encoding.UTF8.GetBytes("testPayload"); + + var payloadHash = SHA256.HashData(payload); + var ecdsaKey = key.LoadWif(); + var owner = FrostFsOwner.FromKey(ecdsaKey); + + var clientKey = new ClientKey(ecdsaKey); + FrostFsContainerId containerId = new("test"); + FrostFsObjectHeader header = new(containerId, FrostFsObjectType.Regular, null, null, owner, new FrostFsVersion(2, 13)); + + var objId = ObjectTools.CalculateObjectId(header, payloadHash, owner, new FrostFsVersion(2, 13), clientKey); + + Assert.NotNull(objId.Value); + Assert.Equal("HuAojwCYi62iUKr1FtSCCkMLLWv1uAnznF8iSb1bRV1N", objId.Value); + } + + [Fact] + public void CalculateObjectIdWithAttrTest() + { + var payload = Encoding.UTF8.GetBytes("testPayload"); + + var payloadHash = SHA256.HashData(payload); + var ecdsaKey = key.LoadWif(); + var owner = FrostFsOwner.FromKey(ecdsaKey); + + var clientKey = new ClientKey(ecdsaKey); + FrostFsContainerId containerId = new("test"); + + FrostFsAttributePair[] attribs = [new("key", "val")]; + + FrostFsObjectHeader header = new(containerId, FrostFsObjectType.Regular, attribs, null, owner, new FrostFsVersion(2, 13)); + + var objId = ObjectTools.CalculateObjectId(header, payloadHash, owner, new FrostFsVersion(2, 13), clientKey); + + Assert.NotNull(objId.Value); + Assert.Equal("4zq5NYEbzkrfmdKne3GnpavE24gU2PnuV17ZExb9hcn3", objId.Value); + } + + [Fact] + public void CalculateObjectIdWithSplitIdTest() + { + var payload = Encoding.UTF8.GetBytes("testPayload"); + + var payloadHash = SHA256.HashData(payload); + var ecdsaKey = key.LoadWif(); + var owner = FrostFsOwner.FromKey(ecdsaKey); + + var clientKey = new ClientKey(ecdsaKey); + FrostFsContainerId containerId = new("test"); + + FrostFsAttributePair[] attribs = [new("key", "val")]; + + var guid = Guid.Parse("790a8d04-f5c3-4cd6-b46f-a78ee7e325f2"); + SplitId splitId = new(guid); + FrostFsSplit split = new (splitId); + + FrostFsObjectHeader header = new(containerId, FrostFsObjectType.Regular, attribs, split, owner, new FrostFsVersion(2, 13)); + + var objId = ObjectTools.CalculateObjectId(header, payloadHash, owner, new FrostFsVersion(2, 13), clientKey); + + Assert.NotNull(objId.Value); + Assert.Equal("HCYzsuXyfe5LmQzi58hPQxExGPAFv7dU5TzEACLxM1os", objId.Value); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs new file mode 100644 index 0000000..9957c63 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs @@ -0,0 +1,307 @@ +using System.Xml.Linq; +using FrostFS.Netmap; +using FrostFS.SDK.Client; +using Google.Protobuf.WellKnownTypes; + +namespace FrostFS.SDK.Tests.Unit; + +public class PlacementPolicyTests : NetworkTestsBase +{ + [Theory] + [InlineData(true, 1)] + [InlineData(true, 3)] + [InlineData(true, 5)] + [InlineData(false, 1)] + [InlineData(false, 3)] + [InlineData(false, 5)] + public void PlacementPolicySimpleFullTest(bool unique, uint backupFactor) + { + PlacementPolicy policy = new() + { + ContainerBackupFactor = backupFactor, + Unique = unique + }; + + var result = policy.ToModel(); + + Assert.Equal(backupFactor, result.BackupFactor); + Assert.Equal(unique, result.Unique); + Assert.Empty(result.Filters); + Assert.Empty(result.Replicas); + Assert.Empty(result.Selectors); + } + + [Fact] + public void PlacementPolicyFullTest() + { + PlacementPolicy policy = new() + { + ContainerBackupFactor = 3, + Unique = true + }; + + policy.Filters.AddRange( + [ + new () { Name = "filter1", Key = "filterKey1", Op =Operation.Eq, Value = "testValue1" }, + new () { Name = "filter2", Key = "filterKey2", Op =Operation.And, Value = "testValue2" } + ]); + + policy.Selectors.AddRange( + [ + new () { Name = "name1", Attribute = "attrib1", Clause = Clause.Same, Count = 5, Filter = "filter1" }, + new () { Name = "name2", Attribute = "attrib2", Clause = Clause.Distinct, Count = 4, Filter = "filter2" } + ]); + + policy.Replicas.AddRange( + [ + new () { EcDataCount = 2, EcParityCount = 3, Count = 4, Selector = "selector1"}, + new () { EcDataCount = 5, EcParityCount = 6, Count = 7, Selector = "selector2"}, + ]); + + var result = policy.ToModel(); + + Assert.Equal(3L, result.BackupFactor); + Assert.True(result.Unique); + Assert.Equal(2, result.Filters.Count); + Assert.Equal(2, result.Replicas.Length); + Assert.Equal(2, result.Selectors.Count); + + var rep0 = result.Replicas[0]; + Assert.Equal(2u, rep0.EcDataCount); + Assert.Equal(3u, rep0.EcParityCount); + Assert.Equal(4, rep0.Count); + Assert.Equal("selector1", rep0.Selector); + + var rep1 = result.Replicas[1]; + Assert.Equal(5u, rep1.EcDataCount); + Assert.Equal(6u, rep1.EcParityCount); + Assert.Equal(7, rep1.Count); + Assert.Equal("selector2", rep1.Selector); + + var f0 = result.Filters[0]; + Assert.Equal("filterKey1", f0.Key); + Assert.Equal("filter1", f0.Name); + Assert.Equal(1, f0.Operation); + Assert.Equal("testValue1", f0.Value); + + var f1 = result.Filters[1]; + Assert.Equal("filterKey2", f1.Key); + Assert.Equal("filter2", f1.Name); + Assert.Equal(8, f1.Operation); + Assert.Equal("testValue2", f1.Value); + + var s0 = result.Selectors[0]; + Assert.Equal("name1", s0.Name); + Assert.Equal("attrib1", s0.Attribute); + Assert.Equal(1, s0.Clause); + Assert.Equal(5L, s0.Count); + Assert.Equal("filter1", s0.Filter); + + var s1 = result.Selectors[1]; + Assert.Equal("name2", s1.Name); + Assert.Equal("attrib2", s1.Attribute); + Assert.Equal(2, s1.Clause); + Assert.Equal(4L, s1.Count); + Assert.Equal("filter2", s1.Filter); + } + + + [Theory] + [InlineData(1, "test" , 0, 0)] + [InlineData(1, "", 1000, 9999)] + [InlineData(1, "some long text to test reasonable length of the selector name", 100000000, 100000001)] + [InlineData(100, "test2", 1, 1)] + [InlineData(1, " ", 2, 3)] + [InlineData(10, "!", 0, 0)] + [InlineData(1, "123", 0, 0)] + public void ReplicaToModelTest(uint count, string selector, uint ecDataCount, uint ecParityCount) + { + Replica replica = new () + { + Count = count, + Selector = selector, + EcDataCount = ecDataCount, + EcParityCount = ecParityCount + }; + + FrostFsReplica model = replica.ToModel(); + + Assert.Equal(count, (uint)model.Count); + Assert.Equal(selector, model.Selector); + Assert.Equal(ecDataCount, model.EcDataCount); + Assert.Equal(ecParityCount, model.EcParityCount); + } + + [Theory] + [InlineData(1, "test", 0, 0)] + [InlineData(1, "", 1000, 9999)] + [InlineData(1, "some long text to test reasonable length of the selector name", 100000000, 100000001)] + [InlineData(100, "test2", 1, 1)] + [InlineData(1, " ", 2, 3)] + [InlineData(10, "!", 0, 0)] + [InlineData(1, "123", 0, 0)] + public void ReplicaToMessagelTest(int count, string selector, uint ecDataCount, uint ecParityCount) + { + FrostFsReplica replica = new () + { + Count = count, + Selector = selector, + EcDataCount = ecDataCount, + EcParityCount = ecParityCount + }; + + Replica message = replica.ToMessage(); + + Assert.Equal((uint)count, message.Count); + Assert.Equal(selector, message.Selector); + Assert.Equal(ecDataCount, message.EcDataCount); + Assert.Equal(ecParityCount, message.EcParityCount); + } + + [Theory] + [InlineData("test", 1, 2, "attribute", "filter")] + [InlineData("test", 0, 0, "longlonglonglonglonglonglonglonglonglonglonglonglong attribute", "longlonglonglonglonglonglonglonglonglonglonglonglong filter")] + [InlineData("test", 0, 1, "attribute", "filter")] + public void SelectorToMessageTest(string name, uint count, int clause, string attr, string filter) + { + FrostFsSelector selector = new (name) + { + Count = count, + Clause = clause, + Attribute = attr, + Filter = filter, + }; + + var message = selector.ToMessage(); + + Assert.Equal(name, message.Name); + Assert.Equal(count, message.Count); + Assert.Equal(clause, (int)message.Clause); + Assert.Equal(attr, message.Attribute); + Assert.Equal(filter, message.Filter); + + } + + [Theory] + [InlineData("test", 1, Clause.Same, "attribute", "filter")] + [InlineData("test", 0, Clause.Distinct, "longlonglonglonglonglonglonglonglonglonglonglonglong attribute", "longlonglonglonglonglonglonglonglonglonglonglonglong filter")] + public void SelectorToModelTest(string name, uint count, Clause clause, string attr, string filter) + { + Selector selector = new () + { + Name = name, + Count = count, + Clause = clause, + Attribute = attr, + Filter = filter + }; + + var model = selector.ToModel(); + + Assert.Equal(name, model.Name); + Assert.Equal(count, model.Count); + Assert.Equal((int)clause, model.Clause); + Assert.Equal(attr, model.Attribute); + Assert.Equal(filter, model.Filter); + } + + [Theory] + [InlineData("", "", 1, "")] + [InlineData("name", "key", 1, "val")] + [InlineData("longlonglonglonglonglonglonglonglonglonglonglonglong name", "longlonglonglonglonglonglonglonglonglonglonglonglong key", 10, "longlonglonglonglonglonglonglonglonglonglonglonglong val")] + public void FilterToMessageTest(string name, string key, int operation, string value) + { + FrostFsFilter filter = new (name, key, operation, value, []); + + var message = filter.ToMessage(); + + Assert.Equal(name, message.Name); + Assert.Equal(key, message.Key); + Assert.Equal(operation, (int)message.Op); + Assert.Equal(value, message.Value); + } + + [Theory] + [InlineData("", "", 1, "")] + [InlineData("name", "key", 2, "val")] + [InlineData("longlonglonglonglonglonglonglonglonglonglonglonglong name", "longlonglonglonglonglonglonglonglonglonglonglonglong key", 10, "longlonglonglonglonglonglonglonglonglonglonglonglong val")] + public void SubFilterToMessageTest(string name, string key, int operation, string value) + { + FrostFsFilter subFilter = new(name, key, operation, value, []); + + FrostFsFilter filter = new("name", "key", 1, "value", [subFilter]); + + var message = filter.ToMessage(); + + Assert.Single(message.Filters); + + var subfilter = message.Filters[0]; + Assert.Equal(name, subfilter.Name); + Assert.Equal(key, subfilter.Key); + Assert.Equal(operation, (int)subfilter.Op); + Assert.Equal(value, subfilter.Value); + } + + [Fact] + public void SubFiltersToMessageTest() + { + string[] names = ["", "name1", "some pretty long name for name test"]; + string[] keys = ["", "key1", "some pretty long key for name test"]; + int[] operations = [1, 2, 10]; + string[] values = ["", "val1", "some pretty long value for name test"]; + + var subFilter = new FrostFsFilter[3]; + + for (int i = 0; i < 3; i++) + { + subFilter[i] = new FrostFsFilter(names[i], keys[i], operations[i], values[i], []); + } + + FrostFsFilter filter = new("name", "key", 1, "value", subFilter); + + var message = filter.ToMessage(); + + Assert.Equal(3, message.Filters.Count); + + for (int i = 0; i < 3; i++) + { + var subfilter = message.Filters[i]; + Assert.Equal(names[i], subfilter.Name); + Assert.Equal(keys[i], subfilter.Key); + Assert.Equal(operations[i], (int)subfilter.Op); + Assert.Equal(values[i], subfilter.Value); + } + } + + [Theory] + [InlineData("", "", Operation.Unspecified, "")] + [InlineData("name", "key", Operation.Unspecified, "val")] + [InlineData("name", "key", Operation.And, "val")] + [InlineData("name", "key", Operation.Eq, "val")] + [InlineData("name", "key", Operation.Le, "val")] + [InlineData("name", "key", Operation.Like, "val")] + [InlineData("name", "key", Operation.Ge, "val")] + [InlineData("name", "key", Operation.Gt, "val")] + [InlineData("name", "key", Operation.Lt, "val")] + [InlineData("name", "key", Operation.Ne, "val")] + [InlineData("name", "key", Operation.Not, "val")] + [InlineData("name", "key", Operation.Or, "val")] + [InlineData("longlonglonglonglonglonglonglonglonglonglonglonglong name", "longlonglonglonglonglonglonglonglonglonglonglonglong key", Operation.Like, "longlonglonglonglonglonglonglonglonglonglonglonglong val")] + public void FrostFsFilterToModelTest(string name, string key, Operation operation, string value) + { + Filter filter = new() + { + Name = name, + Key = key, + Op = operation, + Value = value + }; + + var model = filter.ToModel(); + + Assert.Equal(name, model.Name); + Assert.Equal(key, model.Key); + Assert.Equal((int)operation, model.Operation); + Assert.Equal(value, model.Value); + } +} \ No newline at end of file diff --git a/src/FrostFS.SDK.Tests/Unit/SignatureTests.cs b/src/FrostFS.SDK.Tests/Unit/SignatureTests.cs new file mode 100644 index 0000000..5062319 --- /dev/null +++ b/src/FrostFS.SDK.Tests/Unit/SignatureTests.cs @@ -0,0 +1,39 @@ +using System.Text; +using FrostFS.SDK.Client; +using FrostFS.SDK.Client.Mappers.GRPC; + +namespace FrostFS.SDK.Tests.Unit; + +public class SignatureTests +{ + [Theory] + [InlineData(Refs.SignatureScheme.EcdsaSha512)] + [InlineData(Refs.SignatureScheme.EcdsaRfc6979Sha256)] + [InlineData(Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect)] + + public void SignatureToMessageTest(Refs.SignatureScheme scheme) + { + var key = Encoding.UTF8.GetBytes("datafortest"); + var sign = Encoding.UTF8.GetBytes("signdatafortest"); + + var frostFsScheme = scheme switch + { + Refs.SignatureScheme.EcdsaRfc6979Sha256 => SignatureScheme.EcdsaRfc6979Sha256, + Refs.SignatureScheme.EcdsaRfc6979Sha256WalletConnect => SignatureScheme.EcdsaRfc6979Sha256WalletConnect, + Refs.SignatureScheme.EcdsaSha512 => SignatureScheme.EcdsaSha512 + }; + + FrostFsSignature signature = new() + { + Key = key, + Scheme = frostFsScheme, + Sign = sign + }; + + var result = signature.ToMessage(); + + Assert.Equal(scheme, result.Scheme); + Assert.Equal(sign, result.Sign.ToByteArray()); + Assert.Equal(key, result.Key.ToByteArray()); + } +} \ No newline at end of file From 0816be732a85d0b2509d842f550fb89d6aa1edf3 Mon Sep 17 00:00:00 2001 From: Pavel Gross Date: Wed, 30 Apr 2025 15:19:20 +0300 Subject: [PATCH 65/65] [#69] Fix for Patch and uint types Signed-off-by: Pavel Gross --- src/FrostFS.SDK.Client/AssemblyInfo.cs | 4 +- .../FrostFS.SDK.Client.csproj | 2 +- src/FrostFS.SDK.Client/Mappers/MetaHeader.cs | 4 +- .../Mappers/Netmap/Replica.cs | 4 +- src/FrostFS.SDK.Client/Mappers/Version.cs | 8 +- .../Models/Netmap/FrostFsReplica.cs | 8 +- .../Models/Netmap/FrostFsVersion.cs | 6 +- .../Models/Response/MetaHeader.cs | 6 +- .../Services/ObjectServiceProvider.cs | 163 +++++++++++------- src/FrostFS.SDK.Cryptography/AssemblyInfo.cs | 4 +- .../FrostFS.SDK.Cryptography.csproj | 2 +- src/FrostFS.SDK.Protos/AssemblyInfo.cs | 4 +- .../FrostFS.SDK.Protos.csproj | 2 +- .../Client/ContainerTests/ContainerTests.cs | 4 +- .../Smoke/Client/MiscTests/InterceptorTest.cs | 4 +- .../Smoke/Client/NetworkTests/NetworkTests.cs | 8 +- .../Smoke/Client/ObjectTests/ObjectTests.cs | 94 ++++++---- src/FrostFS.SDK.Tests/Unit/ContainerTest.cs | 4 +- .../Unit/PlacementPolicyTests.cs | 30 ++-- .../Unit/PlacementVectorTests.cs | 4 +- 20 files changed, 212 insertions(+), 153 deletions(-) diff --git a/src/FrostFS.SDK.Client/AssemblyInfo.cs b/src/FrostFS.SDK.Client/AssemblyInfo.cs index 4d87f8c..4fa6cd9 100644 --- a/src/FrostFS.SDK.Client/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Client/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; "e15ab287e6239c98d5dfa91615bd77485d523a3a3f65a4e5028454cedd5ac4d9eca6da18b81985"+ "ac6905d33cc64b5a2587050c16f67b71ef8889dbd3c90ef7cc0b06bbbe09886601d195f5db179a"+ "3c2a25b1")] -[assembly: AssemblyFileVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyProduct("FrostFS.SDK.Client")] [assembly: AssemblyTitle("FrostFS.SDK.Client")] -[assembly: AssemblyVersion("1.0.6")] +[assembly: AssemblyVersion("1.0.7")] diff --git a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj index 037a2f4..88e20c6 100644 --- a/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj +++ b/src/FrostFS.SDK.Client/FrostFS.SDK.Client.csproj @@ -6,7 +6,7 @@ enable AllEnabledByDefault FrostFS.SDK.Client - 1.0.6 + 1.0.7 C# SDK for FrostFS gRPC native protocol diff --git a/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs b/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs index e4ab8a2..b3fa5e3 100644 --- a/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs +++ b/src/FrostFS.SDK.Client/Mappers/MetaHeader.cs @@ -16,8 +16,8 @@ public static class MetaHeaderMapper return new RequestMetaHeader { Version = metaHeader.Version.ToMessage(), - Epoch = (uint)metaHeader.Epoch, - Ttl = (uint)metaHeader.Ttl + Epoch = metaHeader.Epoch, + Ttl = metaHeader.Ttl }; } } \ No newline at end of file diff --git a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs index 00ad4a3..4fa2edd 100644 --- a/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs +++ b/src/FrostFS.SDK.Client/Mappers/Netmap/Replica.cs @@ -11,7 +11,7 @@ public static class PolicyMapper { return new Replica { - Count = (uint)replica.Count, + Count = replica.Count, Selector = replica.Selector, EcDataCount = replica.EcDataCount, EcParityCount = replica.EcParityCount @@ -25,7 +25,7 @@ public static class PolicyMapper throw new ArgumentNullException(nameof(replica)); } - return new FrostFsReplica((int)replica.Count, replica.Selector) + return new FrostFsReplica(replica.Count, replica.Selector) { EcDataCount = replica.EcDataCount, EcParityCount = replica.EcParityCount diff --git a/src/FrostFS.SDK.Client/Mappers/Version.cs b/src/FrostFS.SDK.Client/Mappers/Version.cs index a66df6e..af8738d 100644 --- a/src/FrostFS.SDK.Client/Mappers/Version.cs +++ b/src/FrostFS.SDK.Client/Mappers/Version.cs @@ -18,7 +18,7 @@ public static class VersionMapper throw new System.ArgumentNullException(nameof(model)); } - var key = model.Major << 16 + model.Minor; + var key = (int)model.Major << 16 + (int)model.Minor; if (!_cacheMessages.ContainsKey(key)) { @@ -28,8 +28,8 @@ public static class VersionMapper _spinlock.Enter(ref lockTaken); var message = new Version { - Major = (uint)model.Major, - Minor = (uint)model.Minor + Major = model.Major, + Minor = model.Minor }; _cacheMessages.Add(key, message); @@ -64,7 +64,7 @@ public static class VersionMapper try { _spinlock.Enter(ref lockTaken); - var model = new FrostFsVersion((int)message.Major, (int)message.Minor); + var model = new FrostFsVersion(message.Major, message.Minor); _cacheModels.Add(key, model); return model; diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs index 5ace048..82e2240 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsReplica.cs @@ -4,12 +4,12 @@ namespace FrostFS.SDK; public struct FrostFsReplica : IEquatable { - public int Count { get; set; } + public uint Count { get; set; } public string Selector { get; set; } public uint EcDataCount { get; set; } public uint EcParityCount { get; set; } - public FrostFsReplica(int count, string? selector = null) + public FrostFsReplica(uint count, string? selector = null) { selector ??= string.Empty; @@ -31,12 +31,12 @@ public struct FrostFsReplica : IEquatable public readonly uint CountNodes() { - return Count != 0 ? (uint)Count : EcDataCount + EcParityCount; + return Count != 0 ? Count : EcDataCount + EcParityCount; } public override readonly int GetHashCode() { - return (Count + Selector.GetHashCode()) ^ (int)EcDataCount ^ (int)EcParityCount; + return Count.GetHashCode() ^ Selector.GetHashCode() ^ (int)EcDataCount ^ (int)EcParityCount; } public static bool operator ==(FrostFsReplica left, FrostFsReplica right) diff --git a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs index 81e642d..9ed9522 100644 --- a/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs +++ b/src/FrostFS.SDK.Client/Models/Netmap/FrostFsVersion.cs @@ -3,12 +3,12 @@ using FrostFS.SDK.Client.Mappers.GRPC; namespace FrostFS.SDK; -public class FrostFsVersion(int major, int minor) +public class FrostFsVersion(uint major, uint minor) { private Version? version; - public int Major { get; set; } = major; - public int Minor { get; set; } = minor; + public uint Major { get; set; } = major; + public uint Minor { get; set; } = minor; internal Version VersionID { diff --git a/src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs b/src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs index 36dad09..547b2b5 100644 --- a/src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs +++ b/src/FrostFS.SDK.Client/Models/Response/MetaHeader.cs @@ -1,10 +1,10 @@ namespace FrostFS.SDK; -public class MetaHeader(FrostFsVersion version, int epoch, int ttl) +public class MetaHeader(FrostFsVersion version, ulong epoch, uint ttl) { public FrostFsVersion Version { get; set; } = version; - public int Epoch { get; set; } = epoch; - public int Ttl { get; set; } = ttl; + public ulong Epoch { get; set; } = epoch; + public uint Ttl { get; set; } = ttl; public static MetaHeader Default() { diff --git a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs index a8b748f..b3c1f50 100644 --- a/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs +++ b/src/FrostFS.SDK.Client/Services/ObjectServiceProvider.cs @@ -295,86 +295,88 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl internal async Task PatchObjectAsync(PrmObjectPatch args, CallContext ctx) { var chunkSize = args.MaxChunkLength; - Stream payload = args.Payload ?? throw new ArgumentNullException(nameof(args), "Stream parameter is null"); - + var call = client.Patch(null, ctx.GetDeadline(), ctx.CancellationToken); - byte[]? chunkBuffer = null; - try + var address = new Address { - chunkBuffer = ArrayPool.Shared.Rent(chunkSize); + ObjectId = args.Address.ObjectId, + ContainerId = args.Address.ContainerId + }; - bool isFirstChunk = true; - ulong currentPos = args.Range.Offset; - - var address = new Address + if (args.Payload != null && args.Payload.Length > 0) + { + byte[]? chunkBuffer = null; + try { - ObjectId = args.Address.ObjectId, - ContainerId = args.Address.ContainerId - }; + chunkBuffer = ArrayPool.Shared.Rent(chunkSize); - while (true) - { - var bytesCount = await payload.ReadAsync(chunkBuffer, 0, chunkSize, ctx.CancellationToken).ConfigureAwait(false); + bool isFirstChunk = true; + ulong currentPos = args.Range.Offset; - if (bytesCount == 0) + while (true) { - break; - } + var bytesCount = await args.Payload.ReadAsync(chunkBuffer, 0, chunkSize, ctx.CancellationToken).ConfigureAwait(false); - var request = new PatchRequest() - { - Body = new() + if (bytesCount == 0) { - Address = address, - Patch = new PatchRequest.Types.Body.Types.Patch + break; + } + + PatchRequest request; + + if (isFirstChunk) + { + request = await CreateFirstRequest(args, ctx, address).ConfigureAwait(false); + + request.Body.Patch = new PatchRequest.Types.Body.Types.Patch { Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory(0, bytesCount)), SourceRange = new Range { Offset = currentPos, Length = (ulong)bytesCount } - } + }; + + isFirstChunk = false; } - }; - - if (isFirstChunk) - { - var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); - - var protoToken = sessionToken.CreateObjectTokenContext( - address, - ObjectSessionContext.Types.Verb.Patch, - ClientContext.Key); - - request.AddMetaHeader(args.XHeaders, protoToken); - - if (args.NewAttributes != null && args.NewAttributes.Length > 0) + else { - foreach (var attr in args.NewAttributes) + request = new PatchRequest() { - request.Body.NewAttributes.Add(attr.ToMessage()); - request.Body.ReplaceAttributes = args.ReplaceAttributes; - } + Body = new() + { + Address = address, + Patch = new PatchRequest.Types.Body.Types.Patch + { + Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory(0, bytesCount)), + SourceRange = new Range { Offset = currentPos, Length = (ulong)bytesCount } + } + } + }; + + request.AddMetaHeader(args.XHeaders); } - isFirstChunk = false; + request.Sign(ClientContext.Key); + + await call.RequestStream.WriteAsync(request).ConfigureAwait(false); + + currentPos += (ulong)bytesCount; } - else + } + finally + { + if (chunkBuffer != null) { - request.AddMetaHeader(args.XHeaders); + ArrayPool.Shared.Return(chunkBuffer); } - - request.Sign(ClientContext.Key); - - await call.RequestStream.WriteAsync(request).ConfigureAwait(false); - - currentPos += (ulong)bytesCount; } } - finally + else if (args.NewAttributes != null && args.NewAttributes.Length > 0) { - if (chunkBuffer != null) - { - ArrayPool.Shared.Return(chunkBuffer); - } + PatchRequest request = await CreateFirstRequest(args, ctx, address).ConfigureAwait(false); + + request.Sign(ClientContext.Key); + + await call.RequestStream.WriteAsync(request).ConfigureAwait(false); } await call.RequestStream.CompleteAsync().ConfigureAwait(false); @@ -383,9 +385,36 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl Verifier.CheckResponse(response); return response.Body.ObjectId.ToModel(); + + async Task CreateFirstRequest(PrmObjectPatch args, CallContext ctx, Address address) + { + var body = new PatchRequest.Types.Body() { Address = address }; + + if (args.NewAttributes != null) + { + body.ReplaceAttributes = args.ReplaceAttributes; + + foreach (var attr in args.NewAttributes!) + { + body.NewAttributes.Add(attr.ToMessage()); + } + } + + var request = new PatchRequest() { Body = body }; + + var sessionToken = args.SessionToken ?? await GetDefaultSession(args, ctx).ConfigureAwait(false); + + var protoToken = sessionToken.CreateObjectTokenContext( + address, + ObjectSessionContext.Types.Verb.Patch, + ClientContext.Key); + + request.AddMetaHeader(args.XHeaders, protoToken); + return request; + } } - internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) + internal async Task PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx) { if (args.Payload == null) throw new ArgumentException(nameof(args.Payload)); @@ -431,9 +460,9 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var objectsCount = fullLength > 0 ? (int)(fullLength / (ulong)partSize) + restPart : 0; progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); - + var remain = fullLength; - + byte[]? buffer = null; bool isRentBuffer = false; @@ -443,7 +472,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl { if (args.CustomBuffer.Length < chunkSize) throw new ArgumentException($"Buffer size is too small. At least {chunkSize} required"); - + buffer = args.CustomBuffer; } else @@ -457,7 +486,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl while (remain > 0) { - var bytesToWrite = Math.Min((ulong)partSize, remain); + var bytesToWrite = Math.Min((ulong)partSize, remain); var isLastPart = remain <= (ulong)partSize; // When the last part of the object is uploaded, all metadata for the object must be added @@ -502,7 +531,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var part = new ObjectPartInfo(offset, uploaded, objectId); offset += uploaded; progressInfo.AddPart(part); - + remain -= bytesToWrite; if (isLastPart) @@ -582,16 +611,16 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl var restPart = (fullLength % (ulong)partSize) > 0 ? 1 : 0; var objectsCount = fullLength > 0 ? (int)(fullLength / (ulong)partSize) + restPart : 0; + progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); + // if the object fits one part, it can be loaded as non-complex object, but if it is not upload resuming - if (objectsCount == 1 && progressInfo != null && progressInfo.GetLast().Length == 0) + if (objectsCount == 1 && progressInfo.GetLast().Length == 0) { args.PutObjectContext.MaxObjectSizeCache = partSize; args.PutObjectContext.FullLength = fullLength; var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false); return singlePartResult.ObjectId; } - - progressInfo ??= new UploadProgressInfo(Guid.NewGuid(), objectsCount); var remain = fullLength; @@ -659,8 +688,10 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl offset += size; if (i < objectsCount) + { continue; - + } + // Once all parts of the object are uploaded, they must be linked into a single entity var linkObject = new FrostFsLinkObject(args.Header.ContainerId, progressInfo.SplitId, parentHeader!, [.. progressInfo.GetParts().Select(p => p.ObjectId)]); diff --git a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs index cff5cc1..64bc8e9 100644 --- a/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Cryptography/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Reflection; [assembly: AssemblyCompany("TrueCloudLab")] -[assembly: AssemblyFileVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyProduct("FrostFS.SDK.Cryptography")] [assembly: AssemblyTitle("FrostFS.SDK.Cryptography")] -[assembly: AssemblyVersion("1.0.6.0")] +[assembly: AssemblyVersion("1.0.7.0")] diff --git a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj index 20e0a83..faeabb1 100644 --- a/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj +++ b/src/FrostFS.SDK.Cryptography/FrostFS.SDK.Cryptography.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Cryptography - 1.0.6 + 1.0.7 Cryptography tools for C# SDK diff --git a/src/FrostFS.SDK.Protos/AssemblyInfo.cs b/src/FrostFS.SDK.Protos/AssemblyInfo.cs index f16be42..d7fab05 100644 --- a/src/FrostFS.SDK.Protos/AssemblyInfo.cs +++ b/src/FrostFS.SDK.Protos/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Reflection; [assembly: AssemblyCompany("TrueCloudLab")] -[assembly: AssemblyFileVersion("1.0.6.0")] +[assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyProduct("FrostFS.SDK.Protos")] [assembly: AssemblyTitle("FrostFS.SDK.Protos")] -[assembly: AssemblyVersion("1.0.6.0")] +[assembly: AssemblyVersion("1.0.7.0")] diff --git a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj index b6bc876..222acd1 100644 --- a/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj +++ b/src/FrostFS.SDK.Protos/FrostFS.SDK.Protos.csproj @@ -5,7 +5,7 @@ 12.0 enable FrostFS.SDK.Protos - 1.0.6 + 1.0.7 Protobuf client for C# SDK diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs index 04a8b2c..f1032db 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ContainerTests/ContainerTests.cs @@ -77,7 +77,7 @@ public class ContainerTests : SmokeTestsBase Assert.Empty(container.PlacementPolicy.Value.Filters); Assert.Single(container.PlacementPolicy.Value.Replicas); - Assert.Equal(3, container.PlacementPolicy.Value.Replicas[0].Count); + Assert.Equal(3u, container.PlacementPolicy.Value.Replicas[0].Count); Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcParityCount); Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcDataCount); Assert.Equal("", container.PlacementPolicy.Value.Replicas[0].Selector); @@ -191,7 +191,7 @@ public class ContainerTests : SmokeTestsBase Assert.Empty(subFilter.Filters); Assert.Single(container.PlacementPolicy.Value.Replicas); - Assert.Equal(1, container.PlacementPolicy.Value.Replicas[0].Count); + Assert.Equal(1u, container.PlacementPolicy.Value.Replicas[0].Count); Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcParityCount); Assert.Equal(0u, container.PlacementPolicy.Value.Replicas[0].EcDataCount); Assert.Equal("", container.PlacementPolicy.Value.Replicas[0].Selector); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs index 664d522..9d2fd63 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/MiscTests/InterceptorTest.cs @@ -29,8 +29,8 @@ public class InterceptorTests() : SmokeTestsBase Assert.True(callbackInvoked); Assert.True(interceptorInvoked); - Assert.Equal(2, result.Version.Major); - Assert.Equal(13, result.Version.Minor); + Assert.Equal(2u, result.Version.Major); + Assert.Equal(13u, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.NotNull(result.Addresses); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs index a3d25af..229531a 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/NetworkTests/NetworkTests.cs @@ -28,8 +28,8 @@ public class SmokeClientTests : SmokeTestsBase var result = await client.GetNodeInfoAsync(default); - Assert.Equal(2, result.Version.Major); - Assert.Equal(13, result.Version.Minor); + Assert.Equal(2u, result.Version.Major); + Assert.Equal(13u, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.Single(result.Addresses); @@ -103,8 +103,8 @@ public class SmokeClientTests : SmokeTestsBase Assert.True(callbackInvoked); Assert.True(interceptorInvoked); - Assert.Equal(2, result.Version.Major); - Assert.Equal(13, result.Version.Minor); + Assert.Equal(2u, result.Version.Major); + Assert.Equal(13u, result.Version.Minor); Assert.Equal(NodeState.Online, result.State); Assert.Equal(33, result.PublicKey.Length); Assert.NotNull(result.Addresses); diff --git a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs index a0f501f..473b076 100644 --- a/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs +++ b/src/FrostFS.SDK.Tests/Smoke/Client/ObjectTests/ObjectTests.cs @@ -26,7 +26,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase [InlineData(false, 2, 3)] [InlineData(true, 2, 1)] [InlineData(false, 2, 1)] - public async void FullScenario(bool unique, uint backupFactor, int replicas) + public async void FullScenario(bool unique, uint backupFactor, uint replicas) { var client = FrostFSClient.GetInstance(ClientOptions, GrpcChannel); _testOutputHelper.WriteLine("client created"); @@ -121,9 +121,16 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase await ValidateFilters(client, containerId, objectId, null, (ulong)bytes.Length); _testOutputHelper.WriteLine($"\tfilters validated"); - if (type != clientCut) + if (type != clientCut && bytes.Length > 1024 + 64 && bytes.Length < 20 * 1024 * 1024) { - await ValidatePatch(client, containerId, bytes, objectId); + // patch payload only + await ValidatePatch(client, containerId, bytes, true, objectId, [], false); + + // patch attributes only + await ValidatePatch(client, containerId, bytes, false, objectId, [new("a1", "v1"), new("a2", "v2")], false); + + // patch payload and attributes + await ValidatePatch(client, containerId, bytes, true, objectId, [new("a3", "v3"), new("a4", "v4")], true); _testOutputHelper.WriteLine($"\tpatch validated"); } @@ -199,51 +206,73 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase _testOutputHelper.WriteLine($"\t\trange {range.Offset};{range.Length} validated"); } - private static async Task ValidatePatch(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId) + private static async Task ValidatePatch( + IFrostFSClient client, + FrostFsContainerId containerId, + byte[] bytes, + bool patchPayload, + FrostFsObjectId objectId, + FrostFsAttributePair[] attributes, + bool replaceAttributes) { - if (bytes.Length < 1024 + 64 || bytes.Length > 5900) - return; - - var patch = new byte[1024]; - for (int i = 0; i < patch.Length; i++) + byte[]? patch = null; + FrostFsRange range = new(); + if (patchPayload) { - patch[i] = 32; - } + patch = new byte[1024]; + for (int i = 0; i < patch.Length; i++) + { + patch[i] = 32; + } - var range = new FrostFsRange(64, (ulong)patch.Length); + range = new FrostFsRange(64, (ulong)patch.Length); + } var patchParams = new PrmObjectPatch( new FrostFsAddress(containerId, objectId), - payload: new MemoryStream(patch), + payload: new MemoryStream(patch ?? []), maxChunkLength: 1024, - range: range); + range: range, + replaceAttributes: replaceAttributes, + newAttributes: attributes); var newIbjId = await client.PatchObjectAsync(patchParams, default); var @object = await client.GetObjectAsync(new PrmObjectGet(containerId, newIbjId), default); - var downloadedBytes = new byte[@object.Header.PayloadLength]; - MemoryStream ms = new(downloadedBytes); - - ReadOnlyMemory? chunk; - while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + if (patchPayload) { - ms.Write(chunk.Value.Span); + var downloadedBytes = new byte[@object.Header.PayloadLength]; + MemoryStream ms = new(downloadedBytes); + + ReadOnlyMemory? chunk; + while ((chunk = await @object.ObjectReader!.ReadChunk()) != null) + { + ms.Write(chunk.Value.Span); + } + + for (int i = 0; i < (int)range.Offset; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); + + var rangeEnd = range.Offset + range.Length; + + for (int i = (int)range.Offset; i < (int)rangeEnd; i++) + Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); + + for (int i = (int)rangeEnd; i < bytes.Length; i++) + Assert.Equal(downloadedBytes[i], bytes[i]); } - for (int i = 0; i < (int)range.Offset; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); - - var rangeEnd = range.Offset + range.Length; - - for (int i = (int)range.Offset; i < (int)rangeEnd; i++) - Assert.Equal(downloadedBytes[i], patch[i - (int)range.Offset]); - - for (int i = (int)rangeEnd; i < bytes.Length; i++) - Assert.Equal(downloadedBytes[i], bytes[i]); + if (attributes != null && attributes.Length > 0) + { + foreach (var newAttr in attributes) + { + var i = @object!.Header!.Attributes!.Count(p => p.Key == newAttr.Key && p.Value == newAttr.Value); + Assert.Equal(1, i); + } + } } - private async Task ValidateFilters(IFrostFSClient client, FrostFsContainerId containerId, FrostFsObjectId objectId, SplitId? splitId, ulong length) { var ecdsaKey = keyString.LoadWif(); @@ -318,7 +347,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase Assert.NotNull(objHeader); Assert.Equal(containerId.GetValue(), objHeader.ContainerId.GetValue()); - + Assert.Equal(expected.HeaderInfo!.OwnerId!.Value, objHeader.OwnerId!.Value); Assert.Equal(expected.HeaderInfo.Version!.Major, objHeader.Version!.Major); Assert.Equal(expected.HeaderInfo.Version!.Minor, objHeader.Version!.Minor); @@ -338,7 +367,6 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase Assert.Null(objHeader.Split); } - private static async Task CreateObjectServerCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes) { var header = new FrostFsObjectHeader( diff --git a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs index e713b4a..ce1d06c 100644 --- a/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs +++ b/src/FrostFS.SDK.Tests/Unit/ContainerTest.cs @@ -14,7 +14,7 @@ public class ContainerTest : ContainerTestsBase [Theory] [InlineData(1, "test", 0, 0)] - public void ReplicaToMessagelTest(int count, string selector, uint ecDataCount, uint ecParityCount) + public void ReplicaToMessagelTest(uint count, string selector, uint ecDataCount, uint ecParityCount) { FrostFsReplica replica = new() { @@ -26,7 +26,7 @@ public class ContainerTest : ContainerTestsBase Replica message = replica.ToMessage(); - Assert.Equal((uint)count, message.Count); + Assert.Equal(count, message.Count); Assert.Equal(selector, message.Selector); Assert.Equal(ecDataCount, message.EcDataCount); Assert.Equal(ecParityCount, message.EcParityCount); diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs index 9957c63..46f7a3d 100644 --- a/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/PlacementPolicyTests.cs @@ -69,13 +69,13 @@ public class PlacementPolicyTests : NetworkTestsBase var rep0 = result.Replicas[0]; Assert.Equal(2u, rep0.EcDataCount); Assert.Equal(3u, rep0.EcParityCount); - Assert.Equal(4, rep0.Count); + Assert.Equal(4u, rep0.Count); Assert.Equal("selector1", rep0.Selector); var rep1 = result.Replicas[1]; Assert.Equal(5u, rep1.EcDataCount); Assert.Equal(6u, rep1.EcParityCount); - Assert.Equal(7, rep1.Count); + Assert.Equal(7u, rep1.Count); Assert.Equal("selector2", rep1.Selector); var f0 = result.Filters[0]; @@ -126,7 +126,7 @@ public class PlacementPolicyTests : NetworkTestsBase FrostFsReplica model = replica.ToModel(); - Assert.Equal(count, (uint)model.Count); + Assert.Equal(count, model.Count); Assert.Equal(selector, model.Selector); Assert.Equal(ecDataCount, model.EcDataCount); Assert.Equal(ecParityCount, model.EcParityCount); @@ -140,7 +140,7 @@ public class PlacementPolicyTests : NetworkTestsBase [InlineData(1, " ", 2, 3)] [InlineData(10, "!", 0, 0)] [InlineData(1, "123", 0, 0)] - public void ReplicaToMessagelTest(int count, string selector, uint ecDataCount, uint ecParityCount) + public void ReplicaToMessagelTest(uint count, string selector, uint ecDataCount, uint ecParityCount) { FrostFsReplica replica = new () { @@ -152,7 +152,7 @@ public class PlacementPolicyTests : NetworkTestsBase Replica message = replica.ToMessage(); - Assert.Equal((uint)count, message.Count); + Assert.Equal(count, message.Count); Assert.Equal(selector, message.Selector); Assert.Equal(ecDataCount, message.EcDataCount); Assert.Equal(ecParityCount, message.EcParityCount); @@ -235,11 +235,11 @@ public class PlacementPolicyTests : NetworkTestsBase Assert.Single(message.Filters); - var subfilter = message.Filters[0]; - Assert.Equal(name, subfilter.Name); - Assert.Equal(key, subfilter.Key); - Assert.Equal(operation, (int)subfilter.Op); - Assert.Equal(value, subfilter.Value); + var grpcFilter = message.Filters[0]; + Assert.Equal(name, grpcFilter.Name); + Assert.Equal(key, grpcFilter.Key); + Assert.Equal(operation, (int)grpcFilter.Op); + Assert.Equal(value, grpcFilter.Value); } [Fact] @@ -265,11 +265,11 @@ public class PlacementPolicyTests : NetworkTestsBase for (int i = 0; i < 3; i++) { - var subfilter = message.Filters[i]; - Assert.Equal(names[i], subfilter.Name); - Assert.Equal(keys[i], subfilter.Key); - Assert.Equal(operations[i], (int)subfilter.Op); - Assert.Equal(values[i], subfilter.Value); + var grpcFilter = message.Filters[i]; + Assert.Equal(names[i], grpcFilter.Name); + Assert.Equal(keys[i], grpcFilter.Key); + Assert.Equal(operations[i], (int)grpcFilter.Op); + Assert.Equal(values[i], grpcFilter.Value); } } diff --git a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs index fdf7ffe..9833ae1 100644 --- a/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs +++ b/src/FrostFS.SDK.Tests/Unit/PlacementVectorTests.cs @@ -248,12 +248,12 @@ public class FilterDto Key ?? string.Empty, (int)Op, Value ?? string.Empty, - Filters != null ? Filters.Select(f => f.Filter).ToArray() : []); + Filters != null ? [.. Filters.Select(f => f.Filter)] : []); } public class ReplicaDto { - public int Count { get; set; } + public uint Count { get; set; } public string? Selector { get; set; } }