From 00b2b77b261b195df583916660161d45df4a30ae Mon Sep 17 00:00:00 2001 From: Anton Nikiforov Date: Thu, 2 May 2024 14:29:59 +0300 Subject: [PATCH] [#1112] node: Implement `Range\RangeHash` requests for EC object Signed-off-by: Anton Nikiforov --- go.mod | 4 +- go.sum | 8 +-- pkg/local_object_storage/engine/range.go | 13 ++++ pkg/services/object/get/assemblerec.go | 62 ++++++++++++++++--- .../object/get/v2/get_range_forwarder.go | 3 + pkg/services/object/get/v2/service.go | 3 + pkg/services/object/get/v2/util.go | 11 ++++ 7 files changed, 88 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 1be2526f2..08a59581d 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.21 require ( code.gitea.io/sdk/gitea v0.17.1 - git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240422151450-df9b65324a4c + git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240427200446-67c6f305b21f git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 - git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240424080726-20ab57bf7ec3 + git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240507063414-99e02858af12 git.frostfs.info/TrueCloudLab/hrw v1.2.1 git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240412130734-0e69e485115a git.frostfs.info/TrueCloudLab/tzhash v1.8.0 diff --git a/go.sum b/go.sum index 0795c955b..6200022c7 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= -git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240422151450-df9b65324a4c h1:RFDrNsF2e+EJfaB8lZrRRxNjQkLfM09gnEyudvGuc10= -git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240422151450-df9b65324a4c/go.mod h1:OBDSr+DqV1z4VDouoX3YMleNc4DPBVBWTG3WDT2PK1o= +git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240427200446-67c6f305b21f h1:YyjsQNtrngQzIKOUtApXoi5r5pewatM+cXfpY19vZWo= +git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240427200446-67c6f305b21f/go.mod h1:OBDSr+DqV1z4VDouoX3YMleNc4DPBVBWTG3WDT2PK1o= git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0 h1:FzurjElUwC7InY9v5rzXReKbfBL5yRJKSWJPq6BKhH0= git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.0/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 h1:PaZ8GpnUoXxUoNsc1qp36bT2u7FU+neU4Jn9cl8AWqI= git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65/go.mod h1:6aAX80dvJ3r5fjN9CzzPglRptoiPgIC9KFGGsUA+1Hw= -git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240424080726-20ab57bf7ec3 h1:7Sd/J2IM0uGpmFKBgseUh6/JsdJN06b8W8UZMKAUDZg= -git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240424080726-20ab57bf7ec3/go.mod h1:wDFmMP7l00Xd5VZVzF2MuhyJCnotyhfxHYnvrEEG/e4= +git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240507063414-99e02858af12 h1:uEAn+TBXxgqXXygrYW2X8dFmGydGyJvBc5ysnfHFhYM= +git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240507063414-99e02858af12/go.mod h1:e7H9nNFpx1Tj3R20Zoxy0Vo6Srlb6zV5L7ZQXqg9rn4= git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc= git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM= git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20240412130734-0e69e485115a h1:wbndKvHbwDQiSMQWL75RxiTZCeUyCi7NUj1lsfdAGkc= diff --git a/pkg/local_object_storage/engine/range.go b/pkg/local_object_storage/engine/range.go index e45845d6c..f5b33a251 100644 --- a/pkg/local_object_storage/engine/range.go +++ b/pkg/local_object_storage/engine/range.go @@ -102,6 +102,9 @@ func (e *StorageEngine) getRange(ctx context.Context, prm RngPrm) (RngRes, error if it.SplitInfo != nil { return RngRes{}, logicerr.Wrap(objectSDK.NewSplitInfoError(it.SplitInfo)) } + if it.ECInfo != nil { + return RngRes{}, logicerr.Wrap(objectSDK.NewECInfoError(it.ECInfo)) + } if it.Object == nil { // If any shard is in a degraded mode, we should assume that metabase could store @@ -147,6 +150,8 @@ type getRangeShardIterator struct { Object *objectSDK.Object SplitInfoError *objectSDK.SplitInfoError SplitInfo *objectSDK.SplitInfo + ECInfoError *objectSDK.ECInfoError + ECInfo *objectSDK.ECInfo OutError error ShardWithMeta hashedShard MetaError error @@ -188,6 +193,14 @@ func (i *getRangeShardIterator) tryGetWithMeta(ctx context.Context) { // stop iterating over shards if SplitInfo structure is complete return withLink && withLast + case errors.As(err, &i.ECInfoError): + if i.ECInfo == nil { + i.ECInfo = objectSDK.NewECInfo() + } + + util.MergeECInfo(i.ECInfoError.ECInfo(), i.ECInfo) + // stop iterating over shards if ECInfo structure is complete + return len(i.ECInfo.Chunks) == int(i.ECInfo.Chunks[0].Total) case client.IsErrObjectAlreadyRemoved(err), shard.IsErrOutOfRange(err): diff --git a/pkg/services/object/get/assemblerec.go b/pkg/services/object/get/assemblerec.go index d73d771cf..4a624e467 100644 --- a/pkg/services/object/get/assemblerec.go +++ b/pkg/services/object/get/assemblerec.go @@ -8,6 +8,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/policy" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/erasurecode" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" @@ -45,23 +46,64 @@ func newAssemblerEC( // Assemble assembles erasure-coded object and writes it's content to ObjectWriter. // It returns parent object. func (a *assemblerec) Assemble(ctx context.Context, writer ObjectWriter, headOnly bool) (*objectSDK.Object, error) { - parts := a.retrieveParts(ctx, headOnly) + if headOnly { + return a.reconstructHeader(ctx, writer) + } else if a.rng != nil { + return a.reconstructRange(ctx, writer) + } + return a.reconstructObject(ctx, writer) +} + +func (a *assemblerec) getConstructor() (*erasurecode.Constructor, error) { cnt, err := a.cs.Get(a.addr.Container()) if err != nil { return nil, err } - c, err := erasurecode.NewConstructor( - policy.ECDataCount(cnt.Value.PlacementPolicy()), - policy.ECParityCount(cnt.Value.PlacementPolicy()), - ) + dataCount := policy.ECDataCount(cnt.Value.PlacementPolicy()) + parityCount := policy.ECParityCount(cnt.Value.PlacementPolicy()) + return erasurecode.NewConstructor(dataCount, parityCount) +} + +func (a *assemblerec) reconstructHeader(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { + parts := a.retrieveParts(ctx, true) + c, err := a.getConstructor() if err != nil { return nil, err } - if headOnly { - obj, err := c.ReconstructHeader(parts) - if err == nil { - return obj, writer.WriteHeader(ctx, obj) - } + obj, err := c.ReconstructHeader(parts) + if err == nil { + return obj, writer.WriteHeader(ctx, obj) + } + return nil, err +} + +func (a *assemblerec) reconstructRange(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { + parts := a.retrieveParts(ctx, false) + c, err := a.getConstructor() + if err != nil { + return nil, err + } + obj, err := c.Reconstruct(parts) + if err != nil { + return nil, err + } + + from := a.rng.GetOffset() + to := from + a.rng.GetLength() + if pLen := uint64(len(obj.Payload())); to < from || pLen < from || pLen < to { + return nil, &apistatus.ObjectOutOfRange{} + } + err = writer.WriteChunk(ctx, obj.Payload()[from:to]) + if err != nil { + return nil, err + } + return obj, err +} + +func (a *assemblerec) reconstructObject(ctx context.Context, writer ObjectWriter) (*objectSDK.Object, error) { + parts := a.retrieveParts(ctx, false) + c, err := a.getConstructor() + if err != nil { return nil, err } obj, err := c.Reconstruct(parts) diff --git a/pkg/services/object/get/v2/get_range_forwarder.go b/pkg/services/object/get/v2/get_range_forwarder.go index 8a56c59a6..5b05ec370 100644 --- a/pkg/services/object/get/v2/get_range_forwarder.go +++ b/pkg/services/object/get/v2/get_range_forwarder.go @@ -132,6 +132,9 @@ func (f *getRangeRequestForwarder) readStream(ctx context.Context, rangeStream * case *objectV2.SplitInfo: si := objectSDK.NewSplitInfoFromV2(v) return objectSDK.NewSplitInfoError(si) + case *objectV2.ECInfo: + ei := objectSDK.NewECInfoFromV2(v) + return objectSDK.NewECInfoError(ei) } } return nil diff --git a/pkg/services/object/get/v2/service.go b/pkg/services/object/get/v2/service.go index 682128df6..edd19b441 100644 --- a/pkg/services/object/get/v2/service.go +++ b/pkg/services/object/get/v2/service.go @@ -104,10 +104,13 @@ func (s *Service) GetRange(req *objectV2.GetRangeRequest, stream objectSvc.GetOb err = s.svc.GetRange(stream.Context(), *p) var splitErr *objectSDK.SplitInfoError + var ecErr *objectSDK.ECInfoError switch { case errors.As(err, &splitErr): return stream.Send(splitInfoRangeResponse(splitErr.SplitInfo())) + case errors.As(err, &ecErr): + return stream.Send(ecInfoRangeResponse(ecErr.ECInfo())) default: return err } diff --git a/pkg/services/object/get/v2/util.go b/pkg/services/object/get/v2/util.go index da6428985..610076c7a 100644 --- a/pkg/services/object/get/v2/util.go +++ b/pkg/services/object/get/v2/util.go @@ -292,6 +292,17 @@ func splitInfoRangeResponse(info *objectSDK.SplitInfo) *objectV2.GetRangeRespons return resp } +func ecInfoRangeResponse(info *objectSDK.ECInfo) *objectV2.GetRangeResponse { + resp := new(objectV2.GetRangeResponse) + + body := new(objectV2.GetRangeResponseBody) + resp.SetBody(body) + + body.SetRangePart(info.ToV2()) + + return resp +} + func setSplitInfoHeadResponse(info *objectSDK.SplitInfo, resp *objectV2.HeadResponse) { resp.GetBody().SetHeaderPart(info.ToV2()) }