[#35] Client: rollback to PutSingleObject for client cut upload #46
3 changed files with 187 additions and 88 deletions
|
@ -388,86 +388,129 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
||||||
|
|
||||||
internal async Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx)
|
internal async Task<FrostFsObjectId> PutClientCutObjectAsync(PrmObjectClientCutPut args, CallContext ctx)
|
||||||
{
|
{
|
||||||
var payloadStream = args.Payload!;
|
var stream = args.Payload!;
|
||||||
var header = args.Header!;
|
var header = args.Header!;
|
||||||
|
|
||||||
if (header.PayloadLength > 0)
|
if (header.PayloadLength > 0)
|
||||||
args.PutObjectContext.FullLength = header.PayloadLength;
|
args.PutObjectContext.FullLength = header.PayloadLength;
|
||||||
else if (payloadStream.CanSeek)
|
else if (stream.CanSeek)
|
||||||
args.PutObjectContext.FullLength = (ulong)payloadStream.Length;
|
args.PutObjectContext.FullLength = (ulong)stream.Length;
|
||||||
else
|
else
|
||||||
throw new ArgumentException("The stream does not have a length and payload length is not defined");
|
throw new ArgumentException("The stream does not have a length and payload length is not defined");
|
||||||
|
|
||||||
if (args.PutObjectContext.MaxObjectSizeCache == 0)
|
if (args.PutObjectContext.FullLength == 0)
|
||||||
{
|
throw new ArgumentException("The stream has zero length");
|
||||||
var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
|
var networkSettings = await ClientContext.Client.GetNetworkSettingsAsync(ctx).ConfigureAwait(false);
|
||||||
args.PutObjectContext.MaxObjectSizeCache = (int)networkSettings.MaxObjectSize;
|
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;
|
List<FrostFsObjectId> parts = new(objectsCount);
|
||||||
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.PutObjectContext.FullLength > 0 ? (int)(restBytes / (ulong)objectSize) + restPart : 0;
|
|
||||||
|
|
||||||
List<FrostFsObjectId> sentObjectIds = new(objectsCount);
|
|
||||||
|
|
||||||
FrostFsSplit? split = null;
|
|
||||||
SplitId splitId = new();
|
SplitId splitId = new();
|
||||||
|
|
||||||
|
var partSize = args.PutObjectContext.MaxObjectSizeCache;
|
||||||
|
|
||||||
// keep attributes for the large object
|
// keep attributes for the large object
|
||||||
var attributes = args.Header!.Attributes;
|
var attributes = args.Header!.Attributes.ToArray();
|
||||||
args.Header!.Attributes = null;
|
header.Attributes = null;
|
||||||
|
|
||||||
// send all parts except the last one as separate Objects
|
var remain = args.PutObjectContext.FullLength;
|
||||||
while (restBytes > (ulong)args.PutObjectContext.MaxObjectSizeCache)
|
|
||||||
|
FrostFsObjectHeader? parentHeader = null;
|
||||||
|
|
||||||
|
var lastIndex = objectsCount - 1;
|
||||||
|
|
||||||
|
bool rentBuffer = false;
|
||||||
|
byte[]? buffer = null;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
split = new FrostFsSplit(splitId, sentObjectIds.LastOrDefault());
|
for (int i = 0; i < objectsCount; i++)
|
||||||
|
{
|
||||||
args.Header!.Split = split;
|
if (args.CustomBuffer != null)
|
||||||
|
{
|
||||||
var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false);
|
if (args.CustomBuffer.Length < partSize)
|
||||||
|
{
|
||||||
sentObjectIds.Add(result.ObjectId);
|
throw new ArgumentException($"Buffer size is too small. A buffer with capacity {partSize} is required");
|
||||||
|
|
||||||
restBytes -= (ulong)result.ObjectSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send the last part and create linkObject
|
buffer = args.CustomBuffer;
|
||||||
if (sentObjectIds.Count > 0)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var largeObjectHeader = new FrostFsObjectHeader(
|
buffer = ArrayPool<byte>.Shared.Rent(partSize);
|
||||||
|
rentBuffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesToWrite = Math.Min((ulong)partSize, remain);
|
||||||
|
|
||||||
|
var size = await stream.ReadAsync(buffer, 0, (int)bytesToWrite).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (i == lastIndex)
|
||||||
|
{
|
||||||
|
parentHeader = new FrostFsObjectHeader(header.ContainerId, FrostFsObjectType.Regular, attributes)
|
||||||
|
{
|
||||||
|
PayloadLength = args.PutObjectContext.FullLength
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uploading the next part of the object. Note: the request must contain a non-null SplitId parameter
|
||||||
|
var partHeader = new FrostFsObjectHeader(
|
||||||
header.ContainerId,
|
header.ContainerId,
|
||||||
FrostFsObjectType.Regular,
|
FrostFsObjectType.Regular,
|
||||||
attributes != null ? [.. attributes] : [])
|
[],
|
||||||
|
new FrostFsSplit(splitId, parts.LastOrDefault(),
|
||||||
|
parentHeader: parentHeader))
|
||||||
{
|
{
|
||||||
PayloadLength = args.PutObjectContext.FullLength,
|
PayloadLength = (ulong)size
|
||||||
};
|
};
|
||||||
|
|
||||||
args.Header.Split!.ParentHeader = largeObjectHeader;
|
var obj = new FrostFsObject(partHeader)
|
||||||
|
{
|
||||||
|
SingleObjectPayload = buffer.Length == size ? buffer : buffer[..size]
|
||||||
|
};
|
||||||
|
|
||||||
var result = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false);
|
var prm = new PrmSingleObjectPut(obj);
|
||||||
|
|
||||||
sentObjectIds.Add(result.ObjectId);
|
var objId = await PutSingleObjectAsync(prm, ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
var linkObject = new FrostFsLinkObject(header.ContainerId, split!.SplitId, largeObjectHeader, sentObjectIds);
|
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);
|
_ = await PutSingleObjectAsync(new PrmSingleObjectPut(linkObject), ctx).ConfigureAwait(false);
|
||||||
|
|
||||||
var parentHeader = args.Header.GetHeader();
|
// Retrieve the ID of the linked object
|
||||||
|
return partHeader.GetHeader().Split!.Parent.ToModel();
|
||||||
return parentHeader.Split!.Parent.ToModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are here if the payload is placed to one Object. It means no cut action, just simple PUT.
|
throw new FrostFsException("Unexpected error");
|
||||||
args.Header!.Attributes = attributes;
|
}
|
||||||
|
finally
|
||||||
var singlePartResult = await PutMultipartStreamObjectAsync(args, default).ConfigureAwait(false);
|
{
|
||||||
|
if (rentBuffer && buffer != null)
|
||||||
return singlePartResult.ObjectId;
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PutObjectResult(FrostFsObjectId objectId, int objectSize)
|
struct PutObjectResult(FrostFsObjectId objectId, int objectSize)
|
||||||
|
@ -481,7 +524,6 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
||||||
var payload = args.Payload!;
|
var payload = args.Payload!;
|
||||||
|
|
||||||
var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize;
|
var chunkSize = args.BufferMaxSize > 0 ? args.BufferMaxSize : Constants.ObjectChunkSize;
|
||||||
|
|
||||||
var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition;
|
var restBytes = args.PutObjectContext.FullLength - args.PutObjectContext.CurrentStreamPosition;
|
||||||
|
|
||||||
chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize);
|
chunkSize = (int)Math.Min(restBytes, (ulong)chunkSize);
|
||||||
|
@ -531,17 +573,18 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
||||||
{
|
{
|
||||||
Body = new PutRequest.Types.Body
|
Body = new PutRequest.Types.Body
|
||||||
{
|
{
|
||||||
Chunk = ByteString.CopyFrom(chunkBuffer, 0, bytesCount)
|
Chunk = UnsafeByteOperations.UnsafeWrap(chunkBuffer.AsMemory()[..bytesCount])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
chunkRequest.AddMetaHeader(args.XHeaders);
|
chunkRequest.AddMetaHeader(args.XHeaders);
|
||||||
|
|
||||||
chunkRequest.Sign(ClientContext.Key.ECDsaKey);
|
chunkRequest.Sign(ClientContext.Key.ECDsaKey);
|
||||||
|
|
||||||
await stream.Write(chunkRequest).ConfigureAwait(false);
|
await stream.Write(chunkRequest).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args.PutObjectContext.CurrentStreamPosition += (ulong)sentBytes;
|
||||||
|
|
||||||
var response = await stream.Close().ConfigureAwait(false);
|
var response = await stream.Close().ConfigureAwait(false);
|
||||||
Verifier.CheckResponse(response);
|
Verifier.CheckResponse(response);
|
||||||
|
|
||||||
|
@ -583,7 +626,7 @@ internal sealed class ObjectServiceProvider(ObjectService.ObjectServiceClient cl
|
||||||
{
|
{
|
||||||
Init = new PutRequest.Types.Body.Types.Init
|
Init = new PutRequest.Types.Body.Types.Init
|
||||||
{
|
{
|
||||||
Header = grpcHeader
|
Header = grpcHeader,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,7 +68,7 @@ public static class ObjectTools
|
||||||
{
|
{
|
||||||
Header = grpcHeader,
|
Header = grpcHeader,
|
||||||
ObjectId = new ObjectID { Value = grpcHeader.Sha256() },
|
ObjectId = new ObjectID { Value = grpcHeader.Sha256() },
|
||||||
Payload = ByteString.CopyFrom(@object.SingleObjectPayload)
|
Payload = UnsafeByteOperations.UnsafeWrap(@object.SingleObjectPayload)
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.Signature = new Signature
|
obj.Signature = new Signature
|
||||||
|
|
|
@ -41,7 +41,7 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
backupFactor: backupFactor,
|
backupFactor: backupFactor,
|
||||||
selectors: [],
|
selectors: [],
|
||||||
filter: [],
|
filter: [],
|
||||||
containerAttributes: [],
|
containerAttributes: [new FrostFsAttributePair("contAttrKey", "contAttrValue")],
|
||||||
new FrostFsReplica(replicas));
|
new FrostFsReplica(replicas));
|
||||||
|
|
||||||
Assert.NotNull(containerId);
|
Assert.NotNull(containerId);
|
||||||
|
@ -73,14 +73,17 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
{
|
{
|
||||||
case serverCut:
|
case serverCut:
|
||||||
objectId = await CreateObjectServerCut(client, containerId, bytes);
|
objectId = await CreateObjectServerCut(client, containerId, bytes);
|
||||||
|
_testOutputHelper.WriteLine($"\tserver side cut");
|
||||||
break;
|
break;
|
||||||
case clientCut:
|
case clientCut:
|
||||||
objectId = await CreateObjectClientCut(client, containerId, bytes);
|
objectId = await CreateObjectClientCut(client, containerId, bytes);
|
||||||
|
_testOutputHelper.WriteLine($"\tclient side cut");
|
||||||
break;
|
break;
|
||||||
case singleObject:
|
case singleObject:
|
||||||
if (objectSize > 1 * 1024 * 1024)
|
if (objectSize > 1 * 1024 * 1024)
|
||||||
continue;
|
continue;
|
||||||
objectId = await PutSingleObject(client, containerId, bytes);
|
objectId = await PutSingleObject(client, containerId, bytes);
|
||||||
|
_testOutputHelper.WriteLine($"\tput single object");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -91,17 +94,38 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
|
|
||||||
_testOutputHelper.WriteLine($"\tobject created");
|
_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);
|
await ValidateContent(client, containerId, hash, objectId);
|
||||||
_testOutputHelper.WriteLine($"\tcontent validated");
|
_testOutputHelper.WriteLine($"\tcontent validated");
|
||||||
|
|
||||||
await ValidateFilters(client, containerId, objectId, null, (ulong)bytes.Length);
|
await ValidateFilters(client, containerId, objectId, null, (ulong)bytes.Length);
|
||||||
_testOutputHelper.WriteLine($"\tfilters validated");
|
_testOutputHelper.WriteLine($"\tfilters validated");
|
||||||
|
|
||||||
// if (type != clientCut)
|
if (type != clientCut)
|
||||||
// {
|
{
|
||||||
// await ValidatePatch(client, containerId, bytes, objectId);
|
await ValidatePatch(client, containerId, bytes, objectId);
|
||||||
// _testOutputHelper.WriteLine($"\tpatch validated");
|
_testOutputHelper.WriteLine($"\tpatch validated");
|
||||||
// }
|
}
|
||||||
|
|
||||||
await ValidateRange(client, containerId, bytes, objectId);
|
await ValidateRange(client, containerId, bytes, objectId);
|
||||||
_testOutputHelper.WriteLine($"\trange validated");
|
_testOutputHelper.WriteLine($"\trange validated");
|
||||||
|
@ -139,13 +163,13 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
|
|
||||||
private async Task ValidateRange(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId)
|
private async Task ValidateRange(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes, FrostFsObjectId objectId)
|
||||||
{
|
{
|
||||||
if (bytes.Length < 200)
|
if (bytes.Length < 100)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(0, 50));
|
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)
|
if (bytes.Length >= 6200)
|
||||||
await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(6000, 100));
|
await CheckRange(client, containerId, bytes, objectId, new FrostFsRange(6000, 100));
|
||||||
|
@ -161,11 +185,15 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
MemoryStream ms = new(rangeBytes);
|
MemoryStream ms = new(rangeBytes);
|
||||||
|
|
||||||
ReadOnlyMemory<byte>? chunk;
|
ReadOnlyMemory<byte>? chunk;
|
||||||
|
int readBytes = 0;
|
||||||
while ((chunk = await rangeReader!.ReadChunk()) != null)
|
while ((chunk = await rangeReader!.ReadChunk()) != null)
|
||||||
{
|
{
|
||||||
|
readBytes += chunk.Value.Length;
|
||||||
ms.Write(chunk.Value.Span);
|
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));
|
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");
|
_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)
|
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;
|
return;
|
||||||
|
|
||||||
var patch = new byte[1024];
|
var patch = new byte[1024];
|
||||||
|
@ -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 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));
|
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());
|
await CheckFilter(client, containerId, new FilterByPhysicallyStored());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,6 +306,39 @@ public class ObjectTests(ITestOutputHelper testOutputHelper) : SmokeTestsBase
|
||||||
Assert.Equal(hash, SHA256.HashData(downloadedBytes));
|
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<FrostFsObjectId> CreateObjectServerCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes)
|
private static async Task<FrostFsObjectId> CreateObjectServerCut(IFrostFSClient client, FrostFsContainerId containerId, byte[] bytes)
|
||||||
{
|
{
|
||||||
var header = new FrostFsObjectHeader(
|
var header = new FrostFsObjectHeader(
|
||||||
|
|
Loading…
Add table
Reference in a new issue