[#1] Add Object Head/Put/Delete operations
Signed-off-by: Ivan Pchelintsev <i.pchelintsev@yadro.com>
This commit is contained in:
parent
ee1bf29304
commit
013d8a8436
13 changed files with 192 additions and 12 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
using FrostFS.Object;
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
@ -7,5 +8,8 @@ public interface IFrostFSClient
|
||||||
Task<Container.ListResponse> ListContainersAsync();
|
Task<Container.ListResponse> ListContainersAsync();
|
||||||
Task<Container.PutResponse> CreateContainerAsync(Container.Container container);
|
Task<Container.PutResponse> CreateContainerAsync(Container.Container container);
|
||||||
Task<Container.GetResponse> GetContainerAsync(ContainerID containerId);
|
Task<Container.GetResponse> GetContainerAsync(ContainerID containerId);
|
||||||
Task<Container.DeleteResponse> DeleteContainerAsync(ContainerID cid);
|
Task<Container.DeleteResponse> DeleteContainerAsync(ContainerID containerId);
|
||||||
|
Task<HeadResponse> GetObjectHeadAsync(ContainerID containerId, ObjectID objectId);
|
||||||
|
Task<Object.PutResponse> PutObjectAsync(ContainerID containerId, Stream data);
|
||||||
|
Task<Object.DeleteResponse> DeleteObjectAsync(ContainerID containerId, ObjectID objectId);
|
||||||
}
|
}
|
18
sdk/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs
Normal file
18
sdk/src/FrostFS.SDK.ClientV2/Mappers/GRPC/Object.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using FrostFS.SDK.ModelsV2;
|
||||||
|
using FrostFS.SDK.ModelsV2.Enums;
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
|
||||||
|
public static class ObjectHeadMapper
|
||||||
|
{
|
||||||
|
public static ObjectHead ToModel(this Object.Header head)
|
||||||
|
{
|
||||||
|
// var obtype = Enum.Parse<ObjectType>(head.ObjectType.ToString());
|
||||||
|
return new ObjectHead
|
||||||
|
{
|
||||||
|
ContainerId = ContainerId.FromHash(head.ContainerId.Value.ToByteArray()),
|
||||||
|
Size = (long)head.PayloadLength,
|
||||||
|
Version = head.Version.ToModel()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using FrostFS.Refs;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
|
@ -12,4 +14,27 @@ public static class RequestConstructor
|
||||||
metaHeader ??= MetaHeader.Default().ToGrpcMessage();
|
metaHeader ??= MetaHeader.Default().ToGrpcMessage();
|
||||||
request.MetaHeader = metaHeader;
|
request.MetaHeader = metaHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddObjectSessionToken(
|
||||||
|
this IRequest request,
|
||||||
|
SessionToken sessionToken,
|
||||||
|
ContainerID cid,
|
||||||
|
ObjectID oid,
|
||||||
|
ObjectSessionContext.Types.Verb verb,
|
||||||
|
ECDsa key)
|
||||||
|
{
|
||||||
|
if (request.MetaHeader.SessionToken is not null) return;
|
||||||
|
request.MetaHeader.SessionToken = sessionToken;
|
||||||
|
var ctx = new ObjectSessionContext
|
||||||
|
{
|
||||||
|
Target = new ObjectSessionContext.Types.Target
|
||||||
|
{
|
||||||
|
Container = cid,
|
||||||
|
Objects = { oid }
|
||||||
|
},
|
||||||
|
Verb = verb
|
||||||
|
};
|
||||||
|
request.MetaHeader.SessionToken.Body.Object = ctx;
|
||||||
|
request.MetaHeader.SessionToken.Signature = key.SignMessagePart(request.MetaHeader.SessionToken.Body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
using FrostFS.Object;
|
using FrostFS.Object;
|
||||||
using FrostFS.Refs;
|
using FrostFS.Refs;
|
||||||
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
|
using FrostFS.SDK.Cryptography;
|
||||||
|
using FrostFS.Session;
|
||||||
|
using Google.Protobuf;
|
||||||
|
using Grpc.Core;
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
|
@ -23,6 +28,61 @@ public partial class Client
|
||||||
return await _objectServiceClient.HeadAsync(request);
|
return await _objectServiceClient.HeadAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PutResponse> PutObjectAsync(ContainerID cid, Stream data)
|
||||||
|
{
|
||||||
|
var sessionToken = await CreateSessionAsync(uint.MaxValue);
|
||||||
|
var header = new Header
|
||||||
|
{
|
||||||
|
ContainerId = cid,
|
||||||
|
OwnerId = _owner.ToGrpcMessage(),
|
||||||
|
Version = Version.ToGrpcMessage()
|
||||||
|
};
|
||||||
|
var oid = new ObjectID
|
||||||
|
{
|
||||||
|
Value = header.Sha256()
|
||||||
|
};
|
||||||
|
var request = new PutRequest
|
||||||
|
{
|
||||||
|
Body = new PutRequest.Types.Body
|
||||||
|
{
|
||||||
|
Init = new PutRequest.Types.Body.Types.Init
|
||||||
|
{
|
||||||
|
Header = header,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.AddMetaHeader();
|
||||||
|
request.AddObjectSessionToken(sessionToken, cid, oid, ObjectSessionContext.Types.Verb.Put, _key);
|
||||||
|
request.Sign(_key);
|
||||||
|
|
||||||
|
using var stream = await InitObject(request);
|
||||||
|
var buffer = new byte[ModelsV2.Constants.ObjectChunkSize];
|
||||||
|
var bufferLength = data.Read(buffer, 0, ModelsV2.Constants.ObjectChunkSize);
|
||||||
|
while (bufferLength > 0)
|
||||||
|
{
|
||||||
|
request.Body = new PutRequest.Types.Body
|
||||||
|
{
|
||||||
|
Chunk = ByteString.CopyFrom(buffer[..bufferLength]),
|
||||||
|
};
|
||||||
|
request.VerifyHeader = null;
|
||||||
|
request.Sign(_key);
|
||||||
|
await stream.Write(request);
|
||||||
|
bufferLength = data.Read(buffer, 0, ModelsV2.Constants.ObjectChunkSize);
|
||||||
|
}
|
||||||
|
return await stream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ObjectStreamer> InitObject(PutRequest initRequest)
|
||||||
|
{
|
||||||
|
if (initRequest is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(initRequest));
|
||||||
|
}
|
||||||
|
var call = _objectServiceClient.Put();
|
||||||
|
await call.RequestStream.WriteAsync(initRequest);
|
||||||
|
return new ObjectStreamer { Call = call };
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<DeleteResponse> DeleteObjectAsync(ContainerID cid, ObjectID oid)
|
public async Task<DeleteResponse> DeleteObjectAsync(ContainerID cid, ObjectID oid)
|
||||||
{
|
{
|
||||||
var request = new DeleteRequest
|
var request = new DeleteRequest
|
||||||
|
@ -40,4 +100,30 @@ public partial class Client
|
||||||
request.Sign(_key);
|
request.Sign(_key);
|
||||||
return await _objectServiceClient.DeleteAsync(request);
|
return await _objectServiceClient.DeleteAsync(request);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ObjectStreamer : IDisposable
|
||||||
|
{
|
||||||
|
public AsyncClientStreamingCall<PutRequest, PutResponse> Call { get; init; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Call.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Write(PutRequest request)
|
||||||
|
{
|
||||||
|
if (request is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Call.RequestStream.WriteAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PutResponse> Close()
|
||||||
|
{
|
||||||
|
await Call.RequestStream.CompleteAsync();
|
||||||
|
return await Call.ResponseAsync;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,11 @@
|
||||||
using FrostFS.Refs;
|
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ModelsV2;
|
|
||||||
using FrostFS.Session;
|
using FrostFS.Session;
|
||||||
using Google.Protobuf;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.ClientV2;
|
namespace FrostFS.SDK.ClientV2;
|
||||||
|
|
||||||
public partial class Client
|
public partial class Client
|
||||||
{
|
{
|
||||||
public async Task<SessionToken> CreateSessionAsync(ulong expiration)
|
private async Task<SessionToken> CreateSessionAsync(ulong expiration)
|
||||||
{
|
{
|
||||||
var request = new CreateRequest
|
var request = new CreateRequest
|
||||||
{
|
{
|
||||||
|
@ -23,7 +20,7 @@ public partial class Client
|
||||||
return await CreateSession(request);
|
return await CreateSession(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SessionToken> CreateSession(CreateRequest request)
|
private async Task<SessionToken> CreateSession(CreateRequest request)
|
||||||
{
|
{
|
||||||
var resp = await _sessionServiceClient.CreateAsync(request);
|
var resp = await _sessionServiceClient.CreateAsync(request);
|
||||||
return new SessionToken
|
return new SessionToken
|
||||||
|
|
|
@ -8,8 +8,6 @@ namespace FrostFS.SDK.Cryptography;
|
||||||
|
|
||||||
public static class Helper
|
public static class Helper
|
||||||
{
|
{
|
||||||
public const int Sha256HashLength = 32;
|
|
||||||
|
|
||||||
internal static byte[] RIPEMD160(this byte[] value)
|
internal static byte[] RIPEMD160(this byte[] value)
|
||||||
{
|
{
|
||||||
var hash = new byte[20];
|
var hash = new byte[20];
|
||||||
|
|
|
@ -6,7 +6,6 @@ namespace FrostFS.SDK.Cryptography.Tz
|
||||||
public class GF127 : IEquatable<GF127>
|
public class GF127 : IEquatable<GF127>
|
||||||
{
|
{
|
||||||
public const int ByteSize = 16;
|
public const int ByteSize = 16;
|
||||||
public const ulong MaxUlong = ulong.MaxValue;
|
|
||||||
public const ulong MSB64 = (ulong)1 << 63; // 2^63
|
public const ulong MSB64 = (ulong)1 << 63; // 2^63
|
||||||
public static readonly GF127 Zero = new(0, 0);
|
public static readonly GF127 Zero = new(0, 0);
|
||||||
public static readonly GF127 One = new(1, 0);
|
public static readonly GF127 One = new(1, 0);
|
||||||
|
|
7
sdk/src/FrostFS.SDK.ModelsV2/Constants.cs
Normal file
7
sdk/src/FrostFS.SDK.ModelsV2/Constants.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
public class Constants
|
||||||
|
{
|
||||||
|
public const int ObjectChunkSize = 3 * (1 << 20);
|
||||||
|
public const int Sha256HashLength = 32;
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ public class ContainerId
|
||||||
|
|
||||||
public static ContainerId FromHash(byte[] hash)
|
public static ContainerId FromHash(byte[] hash)
|
||||||
{
|
{
|
||||||
if (hash.Length != Helper.Sha256HashLength)
|
if (hash.Length != Constants.Sha256HashLength)
|
||||||
{
|
{
|
||||||
throw new FormatException("ContainerID must be a sha256 hash.");
|
throw new FormatException("ContainerID must be a sha256 hash.");
|
||||||
}
|
}
|
||||||
|
|
8
sdk/src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs
Normal file
8
sdk/src/FrostFS.SDK.ModelsV2/Enums/ObjectType.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace FrostFS.SDK.ModelsV2.Enums;
|
||||||
|
|
||||||
|
public enum ObjectType
|
||||||
|
{
|
||||||
|
Regular = 1,
|
||||||
|
Tombstone = 2,
|
||||||
|
Lock = 3
|
||||||
|
}
|
9
sdk/src/FrostFS.SDK.ModelsV2/Object.cs
Normal file
9
sdk/src/FrostFS.SDK.ModelsV2/Object.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
namespace FrostFS.SDK.ModelsV2;
|
||||||
|
|
||||||
|
public class ObjectHead
|
||||||
|
{
|
||||||
|
public ContainerId ContainerId { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public Version Version { get; set; }
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ public class ObjectId
|
||||||
|
|
||||||
public static ObjectId FromHash(byte[] hash)
|
public static ObjectId FromHash(byte[] hash)
|
||||||
{
|
{
|
||||||
if (hash.Length != Helper.Sha256HashLength)
|
if (hash.Length != Constants.Sha256HashLength)
|
||||||
{
|
{
|
||||||
throw new FormatException("ObjectID must be a sha256 hash.");
|
throw new FormatException("ObjectID must be a sha256 hash.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
using FrostFS.SDK.ClientV2;
|
using FrostFS.SDK.ClientV2;
|
||||||
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
using FrostFS.SDK.ClientV2.Mappers.GRPC;
|
||||||
using FrostFS.SDK.ModelsV2;
|
using FrostFS.SDK.ModelsV2;
|
||||||
using Google.Protobuf;
|
|
||||||
|
|
||||||
namespace FrostFS.SDK.Service;
|
namespace FrostFS.SDK.Service;
|
||||||
|
|
||||||
|
// TODO: Проверять ответы Grpc
|
||||||
public class FrostFsService
|
public class FrostFsService
|
||||||
{
|
{
|
||||||
private readonly IFrostFSClient _client;
|
private readonly IFrostFSClient _client;
|
||||||
|
@ -42,4 +42,33 @@ public class FrostFsService
|
||||||
{
|
{
|
||||||
var deleteContainerResponse = await _client.DeleteContainerAsync(containerId.ToGrpcMessage());
|
var deleteContainerResponse = await _client.DeleteContainerAsync(containerId.ToGrpcMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ObjectHead> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId)
|
||||||
|
{
|
||||||
|
var getObjectHeadResponse = await _client.GetObjectHeadAsync(
|
||||||
|
containerId.ToGrpcMessage(),
|
||||||
|
objectId.ToGrpcMessage()
|
||||||
|
);
|
||||||
|
return getObjectHeadResponse.Body.Header.Header.ToModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ObjectId> PutObjectAsync(ContainerId containerId, Stream data)
|
||||||
|
{
|
||||||
|
var putObjectResponse = await _client.PutObjectAsync(containerId.ToGrpcMessage(), data);
|
||||||
|
return ObjectId.FromHash(putObjectResponse.Body.ObjectId.Value.ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ObjectId> PutObjectAsync(ContainerId containerId, byte[] data)
|
||||||
|
{
|
||||||
|
var putObjectResponse = await _client.PutObjectAsync(containerId.ToGrpcMessage(), new MemoryStream(data));
|
||||||
|
return ObjectId.FromHash(putObjectResponse.Body.ObjectId.Value.ToByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId)
|
||||||
|
{
|
||||||
|
var deleteObjectResponse = await _client.DeleteObjectAsync(
|
||||||
|
containerId.ToGrpcMessage(),
|
||||||
|
objectId.ToGrpcMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue