diff --git a/api/handler/copy.go b/api/handler/copy.go index 895221a7..52efabe9 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -78,6 +78,27 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { return } + settingsSrc, err := h.obj.GetBucketSettings(r.Context(), srcObjPrm.BktInfo) + if err != nil { + h.logAndSendError(w, "could not get bucket settings", reqInfo, err) + return + } + + if settingsSrc.VersioningEnabled() && srcObjPrm.VersionID == "" { + headObjectPrm := &layer.HeadObjectParams{ + BktInfo: srcObjPrm.BktInfo, + Object: reqInfo.ObjectName, + } + + ei, err := h.obj.GetExtendedObjectInfo(r.Context(), headObjectPrm) + if err != nil { + h.logAndSendError(w, "could not find object", reqInfo, err) + return + } + + srcObjPrm.VersionID = ei.ObjectInfo.VersionID() + } + dstBktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(w, "couldn't get target bucket", reqInfo, err) @@ -157,6 +178,10 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { NodeVersion: extendedSrcObjInfo.NodeVersion, } + if !settingsSrc.VersioningEnabled() { + tagPrm.ObjectVersion.VersionID = "" + } + _, tagSet, err = h.obj.GetObjectTagging(r.Context(), tagPrm) if err != nil { h.logAndSendError(w, "could not get object tagging", reqInfo, err) @@ -252,10 +277,11 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { ObjectName: reqInfo.ObjectName, VersionID: dstObjInfo.VersionID(), }, - TagSet: tagSet, - NodeVersion: extendedDstObjInfo.NodeVersion, + TagSet: tagSet, + NodeVersion: extendedDstObjInfo.NodeVersion, + CopiesNumber: h.cfg.CopiesNumber, } - if _, err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { h.logAndSendError(w, "could not upload object tagging", reqInfo, err) return } diff --git a/api/handler/head.go b/api/handler/head.go index 5b06c3ed..ba5f0015 100644 --- a/api/handler/head.go +++ b/api/handler/head.go @@ -75,6 +75,16 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { VersionID: info.VersionID(), } + bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) + if err != nil { + h.logAndSendError(w, "could not get bucket settings", reqInfo, err) + return + } + + if !bktSettings.VersioningEnabled() { + t.VersionID = "" + } + tagSet, lockInfo, err := h.obj.GetObjectTaggingAndLock(r.Context(), t, extendedInfo.NodeVersion) if err != nil && !s3errors.IsS3Error(err, s3errors.ErrNoSuchKey) { h.logAndSendError(w, "could not get object meta data", reqInfo, err) @@ -103,12 +113,6 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { return } - bktSettings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) - if err != nil { - h.logAndSendError(w, "could not get bucket settings", reqInfo, err) - return - } - writeHeaders(w.Header(), r.Header, extendedInfo, len(tagSet), bktSettings.Unversioned()) w.WriteHeader(http.StatusOK) } diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index f90c1330..8ddeca45 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -433,10 +433,11 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http. ObjectName: objInfo.Name, VersionID: objInfo.VersionID(), }, - TagSet: uploadData.TagSet, - NodeVersion: extendedObjInfo.NodeVersion, + TagSet: uploadData.TagSet, + NodeVersion: extendedObjInfo.NodeVersion, + CopiesNumber: h.cfg.CopiesNumber, } - if _, err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { h.logAndSendError(w, "could not put tagging file of completed multipart upload", reqInfo, err, additional...) return } diff --git a/api/handler/put.go b/api/handler/put.go index 794dbebd..5df15bad 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -318,10 +318,16 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { ObjectName: objInfo.Name, VersionID: objInfo.VersionID(), }, - TagSet: tagSet, - NodeVersion: extendedObjInfo.NodeVersion, + TagSet: tagSet, + NodeVersion: extendedObjInfo.NodeVersion, + CopiesNumber: h.cfg.CopiesNumber, } - if _, err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { + + if !settings.VersioningEnabled() { + tagPrm.ObjectVersion.VersionID = "" + } + + if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { h.logAndSendError(w, "could not upload object tagging", reqInfo, err) return } @@ -540,10 +546,11 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { ObjectName: objInfo.Name, VersionID: objInfo.VersionID(), }, - NodeVersion: extendedObjInfo.NodeVersion, + NodeVersion: extendedObjInfo.NodeVersion, + CopiesNumber: h.cfg.CopiesNumber, } - if _, err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { h.logAndSendError(w, "could not upload object tagging", reqInfo, err) return } diff --git a/api/handler/tagging.go b/api/handler/tagging.go index e034538b..5f2aca6c 100644 --- a/api/handler/tagging.go +++ b/api/handler/tagging.go @@ -45,10 +45,31 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request ObjectName: reqInfo.ObjectName, VersionID: reqInfo.URL.Query().Get(api.QueryVersionID), }, - TagSet: tagSet, + TagSet: tagSet, + CopiesNumber: h.cfg.CopiesNumber, } - nodeVersion, err := h.obj.PutObjectTagging(r.Context(), tagPrm) + settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) if err != nil { + h.logAndSendError(w, "could not get bucket settings", reqInfo, err) + return + } + + if settings.VersioningEnabled() && tagPrm.ObjectVersion.VersionID == "" { + headObjectPrm := &layer.HeadObjectParams{ + BktInfo: bktInfo, + Object: reqInfo.ObjectName, + } + + ei, err := h.obj.GetExtendedObjectInfo(r.Context(), headObjectPrm) + if err != nil { + h.logAndSendError(w, "could not find object", reqInfo, err) + return + } + + tagPrm.ObjectVersion.VersionID = ei.ObjectInfo.VersionID() + } + + if err = h.obj.PutObjectTagging(r.Context(), tagPrm); err != nil { h.logAndSendError(w, "could not put object tagging", reqInfo, err) return } @@ -56,10 +77,7 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request s := &SendNotificationParams{ Event: EventObjectTaggingPut, NotificationInfo: &data.NotificationInfo{ - Name: nodeVersion.FilePath, - Size: nodeVersion.Size, - Version: nodeVersion.OID.EncodeToString(), - HashSum: nodeVersion.ETag, + Name: reqInfo.ObjectName, }, BktInfo: bktInfo, ReqInfo: reqInfo, @@ -94,6 +112,21 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request }, } + if settings.VersioningEnabled() && tagPrm.ObjectVersion.VersionID == "" { + headObjectPrm := &layer.HeadObjectParams{ + BktInfo: bktInfo, + Object: reqInfo.ObjectName, + } + + ei, err := h.obj.GetExtendedObjectInfo(r.Context(), headObjectPrm) + if err != nil { + h.logAndSendError(w, "could not find object", reqInfo, err) + return + } + + tagPrm.ObjectVersion.VersionID = ei.ObjectInfo.VersionID() + } + versionID, tagSet, err := h.obj.GetObjectTagging(r.Context(), tagPrm) if err != nil { h.logAndSendError(w, "could not get object tagging", reqInfo, err) @@ -123,8 +156,28 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ VersionID: reqInfo.URL.Query().Get(api.QueryVersionID), } - nodeVersion, err := h.obj.DeleteObjectTagging(r.Context(), p) + settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) if err != nil { + h.logAndSendError(w, "could not get bucket settings", reqInfo, err) + return + } + + if settings.VersioningEnabled() && p.VersionID == "" { + headObjectPrm := &layer.HeadObjectParams{ + BktInfo: bktInfo, + Object: reqInfo.ObjectName, + } + + ei, err := h.obj.GetExtendedObjectInfo(r.Context(), headObjectPrm) + if err != nil { + h.logAndSendError(w, "could not find object", reqInfo, err) + return + } + + p.VersionID = ei.ObjectInfo.VersionID() + } + + if err = h.obj.DeleteObjectTagging(r.Context(), p); err != nil { h.logAndSendError(w, "could not delete object tagging", reqInfo, err) return } @@ -132,10 +185,7 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ s := &SendNotificationParams{ Event: EventObjectTaggingDelete, NotificationInfo: &data.NotificationInfo{ - Name: nodeVersion.FilePath, - Size: nodeVersion.Size, - Version: nodeVersion.OID.EncodeToString(), - HashSum: nodeVersion.ETag, + Name: reqInfo.ObjectName, }, BktInfo: bktInfo, ReqInfo: reqInfo, diff --git a/api/layer/compound.go b/api/layer/compound.go index 4ed9b643..7e4c75d0 100644 --- a/api/layer/compound.go +++ b/api/layer/compound.go @@ -19,14 +19,11 @@ func (n *layer) GetObjectTaggingAndLock(ctx context.Context, objVersion *ObjectV return tags, lockInfo, nil } - if nodeVersion == nil { - nodeVersion, err = n.getNodeVersion(ctx, objVersion) - if err != nil { - return nil, nil, err - } + var p = GetObjectTaggingParams{ + ObjectVersion: objVersion, } - tags, err = n.treeService.GetObjectTagging(ctx, objVersion.BktInfo, nodeVersion) + _, tags, err = n.GetObjectTagging(ctx, &p) if err != nil { if errorsStd.Is(err, ErrNodeNotFound) { return nil, nil, s3errors.GetAPIError(s3errors.ErrNoSuchKey) diff --git a/api/layer/layer.go b/api/layer/layer.go index 1410fc0a..500e0b6b 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -216,8 +216,8 @@ type ( DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error GetObjectTagging(ctx context.Context, p *GetObjectTaggingParams) (string, map[string]string, error) - PutObjectTagging(ctx context.Context, p *PutObjectTaggingParams) (*data.NodeVersion, error) - DeleteObjectTagging(ctx context.Context, p *ObjectVersion) (*data.NodeVersion, error) + PutObjectTagging(ctx context.Context, p *PutObjectTaggingParams) error + DeleteObjectTagging(ctx context.Context, p *ObjectVersion) error PutObject(ctx context.Context, p *PutObjectParams) (*data.ExtendedObjectInfo, error) diff --git a/api/layer/multipart_upload.go b/api/layer/multipart_upload.go index 8d5414fa..32ada6d2 100644 --- a/api/layer/multipart_upload.go +++ b/api/layer/multipart_upload.go @@ -1027,10 +1027,6 @@ func (n *layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar IsUnversioned: !bktSettings.VersioningEnabled(), } - if newVersion.ID, err = n.treeService.AddVersion(ctx, p.Info.Bkt, newVersion); err != nil { - return nil, nil, fmt.Errorf("couldn't add multipart new verion to tree service: %w", err) - } - n.cache.CleanListCacheEntriesContainingObject(p.Info.Key, p.Info.Bkt.CID) objInfo := &data.ObjectInfo{ diff --git a/api/layer/neofs_mock.go b/api/layer/neofs_mock.go index b393a9c4..586c55be 100644 --- a/api/layer/neofs_mock.go +++ b/api/layer/neofs_mock.go @@ -491,40 +491,42 @@ func (t *TestNeoFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]oid } for _, obj := range t.objects { - var isOk = true - - for _, attr := range obj.Attributes() { - for _, f := range prm.Filters { - if attr.Key() == f.Header() { - switch f.Operation() { - case object.MatchStringEqual: - if f.Header() == "$Object:objectType" { - isOk = isOk && obj.Type().String() == f.Value() - } else { - isOk = isOk && attr.Value() == f.Value() - } - case object.MatchStringNotEqual: - isOk = isOk && attr.Value() != f.Value() - case object.MatchCommonPrefix: - isOk = isOk && strings.HasPrefix(attr.Value(), f.Value()) - case object.MatchNotPresent: - isOk = false - default: - isOk = false - } - } - - if f.Header() == "$Object:objectType" { - isOk = isOk && obj.Type().String() == f.Value() - } - } - } - - // all filters are valid for obj. - if isOk { + if checkFilters(obj, prm.Filters) { oids = append(oids, obj.GetID()) } } return oids, nil } + +func checkFilters(obj *object.Object, filters object.SearchFilters) bool { + var isOk = true + + attrs := make(map[string]string, len(obj.Attributes())) + for _, attr := range obj.Attributes() { + attrs[attr.Key()] = attr.Value() + } + + for _, f := range filters { + val, present := attrs[f.Header()] + + switch f.Operation() { + case object.MatchStringEqual: + if f.Header() == "$Object:objectType" { + isOk = isOk && obj.Type().String() == f.Value() + } else { + isOk = isOk && val == f.Value() + } + case object.MatchStringNotEqual: + isOk = isOk && val != f.Value() + case object.MatchCommonPrefix: + isOk = isOk && strings.HasPrefix(val, f.Value()) + case object.MatchNotPresent: + isOk = !present + default: + isOk = false + } + } + + return isOk +} diff --git a/api/layer/object.go b/api/layer/object.go index f17b1ce6..bebfebce 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -271,9 +271,6 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend newVersion.OID = id newVersion.ETag = hex.EncodeToString(hash) - if newVersion.ID, err = n.treeService.AddVersion(ctx, p.BktInfo, newVersion); err != nil { - return nil, fmt.Errorf("couldn't add new verion to tree service: %w", err) - } if p.Lock != nil && (p.Lock.Retention != nil || p.Lock.LegalHold != nil) { putLockInfoPrms := &PutLockInfoParams{ @@ -283,7 +280,6 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Extend }, NewLock: p.Lock, CopiesNumber: p.CopiesNumber, - NodeVersion: newVersion, // provide new version to make one less tree service call in PutLockInfo } if bktSettings.VersioningEnabled() { @@ -414,11 +410,12 @@ func (n *layer) headLastVersionIfNotDeleted(ctx context.Context, bkt *data.Bucke func (n *layer) searchAllVersionsInNeoFS(ctx context.Context, bkt *data.BucketInfo, owner user.ID, objectName string, onlyUnversioned bool) ([]*object.Object, error) { prmSearch := PrmObjectSearch{ Container: bkt.CID, - Filters: make(object.SearchFilters, 0, 3), + Filters: make(object.SearchFilters, 0, 4), } n.prepareAuthParameters(ctx, &prmSearch.PrmAuth, owner) prmSearch.Filters.AddTypeFilter(object.MatchStringEqual, object.TypeRegular) + prmSearch.Filters.AddFilter(attributeTagsMetaObject, "", object.MatchNotPresent) if len(objectName) > 0 { prmSearch.Filters.AddFilter(object.AttributeFilePath, objectName, object.MatchStringEqual) @@ -437,11 +434,12 @@ func (n *layer) searchAllVersionsInNeoFS(ctx context.Context, bkt *data.BucketIn func (n *layer) searchAllVersionsInNeoFSByPrefix(ctx context.Context, bkt *data.BucketInfo, owner user.ID, prefix string, onlyUnversioned bool) ([]*object.Object, error) { prmSearch := PrmObjectSearch{ Container: bkt.CID, - Filters: make(object.SearchFilters, 0, 3), + Filters: make(object.SearchFilters, 0, 4), } n.prepareAuthParameters(ctx, &prmSearch.PrmAuth, owner) prmSearch.Filters.AddTypeFilter(object.MatchStringEqual, object.TypeRegular) + prmSearch.Filters.AddFilter(attributeTagsMetaObject, "", object.MatchNotPresent) if len(prefix) > 0 { prmSearch.Filters.AddFilter(object.AttributeFilePath, prefix, object.MatchCommonPrefix) diff --git a/api/layer/system_object.go b/api/layer/system_object.go index 54ca3721..6a4b4ad6 100644 --- a/api/layer/system_object.go +++ b/api/layer/system_object.go @@ -33,15 +33,6 @@ type PutLockInfoParams struct { func (n *layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err error) { newLock := p.NewLock - versionNode := p.NodeVersion - // sometimes node version can be provided from executing context - // if not, then receive node version from tree service - if versionNode == nil { - versionNode, err = n.getNodeVersionFromCacheOrNeofs(ctx, p.ObjVersion) - if err != nil { - return err - } - } lockInfo, err := n.getLockDataFromObjects(ctx, p.ObjVersion.BktInfo, p.ObjVersion.ObjectName, p.ObjVersion.VersionID) if err != nil && !errorsStd.Is(err, ErrNodeNotFound) { @@ -52,6 +43,13 @@ func (n *layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err erro lockInfo = &data.LockInfo{} } + objList, err := n.searchAllVersionsInNeoFS(ctx, p.ObjVersion.BktInfo, p.ObjVersion.BktInfo.Owner, p.ObjVersion.ObjectName, p.ObjVersion.VersionID == "") + if err != nil { + return err + } + + objectToLock := objList[0].GetID() + if newLock.Retention != nil { if lockInfo.IsRetentionSet() { if lockInfo.IsCompliance() { @@ -73,7 +71,7 @@ func (n *layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err erro } } lock := &data.ObjectLock{Retention: newLock.Retention} - retentionOID, err := n.putLockObject(ctx, p.ObjVersion.BktInfo, versionNode.OID, lock, p.CopiesNumber, p.ObjVersion.ObjectName, p.ObjVersion.VersionID) + retentionOID, err := n.putLockObject(ctx, p.ObjVersion.BktInfo, objectToLock, lock, p.CopiesNumber, p.ObjVersion.ObjectName, p.ObjVersion.VersionID) if err != nil { return err } @@ -83,7 +81,7 @@ func (n *layer) PutLockInfo(ctx context.Context, p *PutLockInfoParams) (err erro if newLock.LegalHold != nil { if newLock.LegalHold.Enabled && !lockInfo.IsLegalHoldSet() { lock := &data.ObjectLock{LegalHold: newLock.LegalHold} - legalHoldOID, err := n.putLockObject(ctx, p.ObjVersion.BktInfo, versionNode.OID, lock, p.CopiesNumber, p.ObjVersion.ObjectName, p.ObjVersion.VersionID) + legalHoldOID, err := n.putLockObject(ctx, p.ObjVersion.BktInfo, objectToLock, lock, p.CopiesNumber, p.ObjVersion.ObjectName, p.ObjVersion.VersionID) if err != nil { return err } @@ -181,17 +179,6 @@ func (n *layer) getLockDataFromObjects(ctx context.Context, bkt *data.BucketInfo return &lock, nil } -func (n *layer) getNodeVersionFromCacheOrNeofs(ctx context.Context, objVersion *ObjectVersion) (nodeVersion *data.NodeVersion, err error) { - // check cache if node version is stored inside extendedObjectVersion - nodeVersion = n.getNodeVersionFromCache(n.Owner(ctx), objVersion) - if nodeVersion == nil { - // else get node version from tree service - return n.getNodeVersion(ctx, objVersion) - } - - return nodeVersion, nil -} - func (n *layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, lock *data.ObjectLock, copiesNumber uint32, objectName, objectVersion string) (oid.ID, error) { prm := PrmObjectCreate{ Container: bktInfo.CID, diff --git a/api/layer/tagging.go b/api/layer/tagging.go index be1551c1..b63fea92 100644 --- a/api/layer/tagging.go +++ b/api/layer/tagging.go @@ -1,15 +1,18 @@ package layer import ( + "bytes" "context" + "encoding/json" errorsStd "errors" + "fmt" + "slices" - "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/data" "github.com/nspcc-dev/neofs-s3-gw/api/s3errors" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" - oid "github.com/nspcc-dev/neofs-sdk-go/object/id" - "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/object" "go.uber.org/zap" ) @@ -24,10 +27,49 @@ type PutObjectTaggingParams struct { ObjectVersion *ObjectVersion TagSet map[string]string + CopiesNumber uint32 + // NodeVersion can be nil. If not nil we save one request to tree service. NodeVersion *data.NodeVersion // optional } +const ( + attributeTagsMetaObject = ".s3-tags-meta-object" + attributeTagsMetaObjectValue = "true" +) + +func (n *layer) PutObjectTagging(ctx context.Context, p *PutObjectTaggingParams) error { + payload, err := json.Marshal(p.TagSet) + if err != nil { + return fmt.Errorf("could not marshal tag set: %w", err) + } + + prm := PrmObjectCreate{ + Container: p.ObjectVersion.BktInfo.CID, + Creator: p.ObjectVersion.BktInfo.Owner, + CreationTime: TimeNow(ctx), + CopiesNumber: p.CopiesNumber, + Filepath: p.ObjectVersion.ObjectName, + Attributes: make(map[string]string, 2), + Payload: bytes.NewBuffer(payload), + PayloadSize: uint64(len(payload)), + } + + if p.ObjectVersion.VersionID != "" { + prm.Attributes[AttributeObjectVersion] = p.ObjectVersion.VersionID + } + + prm.Attributes[attributeTagsMetaObject] = attributeTagsMetaObjectValue + + if _, _, err = n.objectPutAndHash(ctx, prm, p.ObjectVersion.BktInfo); err != nil { + return fmt.Errorf("create tagging object: %w", err) + } + + n.cache.PutTagging(n.Owner(ctx), objectTaggingCacheKey(p.ObjectVersion), p.TagSet) + + return nil +} + func (n *layer) GetObjectTagging(ctx context.Context, p *GetObjectTaggingParams) (string, map[string]string, error) { var err error owner := n.Owner(ctx) @@ -38,74 +80,100 @@ func (n *layer) GetObjectTagging(ctx context.Context, p *GetObjectTaggingParams) } } - nodeVersion := p.NodeVersion - if nodeVersion == nil { - nodeVersion, err = n.getNodeVersionFromCacheOrNeofs(ctx, p.ObjectVersion) - if err != nil { - return "", nil, err - } + prmSearch := PrmObjectSearch{ + Container: p.ObjectVersion.BktInfo.CID, + Filters: make(object.SearchFilters, 0, 3), } - p.ObjectVersion.VersionID = nodeVersion.OID.EncodeToString() - if tags := n.cache.GetTagging(owner, objectTaggingCacheKey(p.ObjectVersion)); tags != nil { - return p.ObjectVersion.VersionID, tags, nil + n.prepareAuthParameters(ctx, &prmSearch.PrmAuth, p.ObjectVersion.BktInfo.Owner) + prmSearch.Filters.AddFilter(object.AttributeFilePath, p.ObjectVersion.ObjectName, object.MatchStringEqual) + prmSearch.Filters.AddFilter(attributeTagsMetaObject, attributeTagsMetaObjectValue, object.MatchStringEqual) + prmSearch.Filters.AddTypeFilter(object.MatchStringEqual, object.TypeRegular) + if p.ObjectVersion.VersionID != "" { + prmSearch.Filters.AddFilter(AttributeObjectVersion, p.ObjectVersion.VersionID, object.MatchStringEqual) } - tags, err := n.treeService.GetObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion) + ids, err := n.neoFS.SearchObjects(ctx, prmSearch) if err != nil { - if errorsStd.Is(err, ErrNodeNotFound) { - return "", nil, s3errors.GetAPIError(s3errors.ErrNoSuchKey) + if errorsStd.Is(err, apistatus.ErrObjectAccessDenied) { + return "", nil, s3errors.GetAPIError(s3errors.ErrAccessDenied) } - return "", nil, err + + return "", nil, fmt.Errorf("search object version: %w", err) } - n.cache.PutTagging(owner, objectTaggingCacheKey(p.ObjectVersion), tags) + if len(ids) == 0 { + return "", nil, nil + } - return p.ObjectVersion.VersionID, tags, nil -} + var ( + objects = make([]*object.Object, 0, len(ids)) + ) -func (n *layer) PutObjectTagging(ctx context.Context, p *PutObjectTaggingParams) (nodeVersion *data.NodeVersion, err error) { - nodeVersion = p.NodeVersion - if nodeVersion == nil { - nodeVersion, err = n.getNodeVersionFromCacheOrNeofs(ctx, p.ObjectVersion) + for i := range ids { + obj, err := n.objectGet(ctx, p.ObjectVersion.BktInfo, ids[i]) if err != nil { - return nil, err - } - } - p.ObjectVersion.VersionID = nodeVersion.OID.EncodeToString() + n.log.Warn("couldn't obj object", + zap.Stringer("oid", &ids[i]), + zap.Stringer("cid", p.ObjectVersion.BktInfo.CID), + zap.Error(err)) - err = n.treeService.PutObjectTagging(ctx, p.ObjectVersion.BktInfo, nodeVersion, p.TagSet) - if err != nil { - if errorsStd.Is(err, ErrNodeNotFound) { - return nil, s3errors.GetAPIError(s3errors.ErrNoSuchKey) + return "", nil, fmt.Errorf("couldn't obj object: %w", err) } - return nil, err + + objects = append(objects, obj) } - n.cache.PutTagging(n.Owner(ctx), objectTaggingCacheKey(p.ObjectVersion), p.TagSet) + slices.SortFunc(objects, sortObjectsFunc) + + var ( + lastObj = objects[0] + tags = make(map[string]string) + ) - return nodeVersion, nil + if err = json.Unmarshal(lastObj.Payload(), &tags); err != nil { + return "", nil, fmt.Errorf("couldn't unmarshal last object: %w", err) + } + + return p.ObjectVersion.VersionID, tags, nil } -func (n *layer) DeleteObjectTagging(ctx context.Context, p *ObjectVersion) (*data.NodeVersion, error) { - version, err := n.getNodeVersion(ctx, p) - if err != nil { - return nil, err +func (n *layer) DeleteObjectTagging(ctx context.Context, p *ObjectVersion) error { + prmSearch := PrmObjectSearch{ + Container: p.BktInfo.CID, + Filters: make(object.SearchFilters, 0, 3), + } + + n.prepareAuthParameters(ctx, &prmSearch.PrmAuth, p.BktInfo.Owner) + prmSearch.Filters.AddFilter(object.AttributeFilePath, p.ObjectName, object.MatchStringEqual) + prmSearch.Filters.AddFilter(attributeTagsMetaObject, "true", object.MatchStringEqual) + prmSearch.Filters.AddTypeFilter(object.MatchStringEqual, object.TypeRegular) + if p.VersionID != "" { + prmSearch.Filters.AddFilter(AttributeObjectVersion, p.VersionID, object.MatchStringEqual) } - err = n.treeService.DeleteObjectTagging(ctx, p.BktInfo, version) + ids, err := n.neoFS.SearchObjects(ctx, prmSearch) if err != nil { - if errorsStd.Is(err, ErrNodeNotFound) { - return nil, s3errors.GetAPIError(s3errors.ErrNoSuchKey) + if errorsStd.Is(err, apistatus.ErrObjectAccessDenied) { + return s3errors.GetAPIError(s3errors.ErrAccessDenied) } - return nil, err + + return fmt.Errorf("search object version: %w", err) + } + + if len(ids) == 0 { + return nil } - p.VersionID = version.OID.EncodeToString() + for _, id := range ids { + if err = n.objectDelete(ctx, p.BktInfo, id); err != nil { + return fmt.Errorf("couldn't delete object: %w", err) + } + } n.cache.DeleteTagging(objectTaggingCacheKey(p)) - return version, nil + return nil } func (n *layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) { @@ -156,64 +224,3 @@ func objectTaggingCacheKey(p *ObjectVersion) string { func bucketTaggingCacheKey(cnrID cid.ID) string { return ".tagset." + cnrID.EncodeToString() } - -func (n *layer) getNodeVersion(ctx context.Context, objVersion *ObjectVersion) (*data.NodeVersion, error) { - var err error - var version *data.NodeVersion - - if objVersion.VersionID == data.UnversionedObjectVersionID { - version, err = n.treeService.GetUnversioned(ctx, objVersion.BktInfo, objVersion.ObjectName) - } else if len(objVersion.VersionID) == 0 { - version, err = n.treeService.GetLatestVersion(ctx, objVersion.BktInfo, objVersion.ObjectName) - } else { - versions, err2 := n.treeService.GetVersions(ctx, objVersion.BktInfo, objVersion.ObjectName) - if err2 != nil { - return nil, err2 - } - for _, v := range versions { - if v.OID.EncodeToString() == objVersion.VersionID { - version = v - break - } - } - if version == nil { - err = s3errors.GetAPIError(s3errors.ErrNoSuchVersion) - } - } - - if err == nil && version.IsDeleteMarker() && !objVersion.NoErrorOnDeleteMarker || errorsStd.Is(err, ErrNodeNotFound) { - return nil, s3errors.GetAPIError(s3errors.ErrNoSuchKey) - } - - if err == nil && version != nil && !version.IsDeleteMarker() { - reqInfo := api.GetReqInfo(ctx) - n.log.Debug("target details", - zap.String("reqId", reqInfo.RequestID), - zap.String("bucket", objVersion.BktInfo.Name), zap.Stringer("cid", objVersion.BktInfo.CID), - zap.String("object", objVersion.ObjectName), zap.Stringer("oid", version.OID)) - } - - return version, err -} - -func (n *layer) getNodeVersionFromCache(owner user.ID, o *ObjectVersion) *data.NodeVersion { - if len(o.VersionID) == 0 || o.VersionID == data.UnversionedObjectVersionID { - return nil - } - - var objID oid.ID - if objID.DecodeString(o.VersionID) != nil { - return nil - } - - var addr oid.Address - addr.SetContainer(o.BktInfo.CID) - addr.SetObject(objID) - - extObjectInfo := n.cache.GetObject(owner, addr) - if extObjectInfo == nil { - return nil - } - - return extObjectInfo.NodeVersion -} diff --git a/api/layer/tree_mock.go b/api/layer/tree_mock.go index f5eb3ec5..136d89a5 100644 --- a/api/layer/tree_mock.go +++ b/api/layer/tree_mock.go @@ -1,7 +1,6 @@ package layer import ( - "cmp" "context" "fmt" "slices" @@ -20,39 +19,6 @@ type TreeServiceMock struct { parts map[string]map[int]*data.PartInfo } -func (t *TreeServiceMock) GetObjectTagging(_ context.Context, bktInfo *data.BucketInfo, nodeVersion *data.NodeVersion) (map[string]string, error) { - cnrTagsMap, ok := t.tags[bktInfo.CID.EncodeToString()] - if !ok { - return nil, nil - } - - return cnrTagsMap[nodeVersion.ID], nil -} - -func (t *TreeServiceMock) PutObjectTagging(_ context.Context, bktInfo *data.BucketInfo, nodeVersion *data.NodeVersion, tagSet map[string]string) error { - cnrTagsMap, ok := t.tags[bktInfo.CID.EncodeToString()] - if !ok { - t.tags[bktInfo.CID.EncodeToString()] = map[uint64]map[string]string{ - nodeVersion.ID: tagSet, - } - return nil - } - - cnrTagsMap[nodeVersion.ID] = tagSet - - return nil -} - -func (t *TreeServiceMock) DeleteObjectTagging(_ context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) error { - cnrTagsMap, ok := t.tags[bktInfo.CID.EncodeToString()] - if !ok { - return nil - } - - delete(cnrTagsMap, objVersion.ID) - return nil -} - func (t *TreeServiceMock) GetBucketTagging(_ context.Context, _ *data.BucketInfo) (map[string]string, error) { // TODO implement me panic("implement me") @@ -114,102 +80,6 @@ func (t *TreeServiceMock) DeleteBucketCORS(_ context.Context, _ *data.BucketInfo panic("implement me") } -func (t *TreeServiceMock) GetVersions(_ context.Context, bktInfo *data.BucketInfo, objectName string) ([]*data.NodeVersion, error) { - cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()] - if !ok { - return nil, ErrNodeNotFound - } - - versions, ok := cnrVersionsMap[objectName] - if !ok { - return nil, ErrNodeNotFound - } - - return versions, nil -} - -func (t *TreeServiceMock) GetLatestVersion(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) { - cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()] - if !ok { - return nil, ErrNodeNotFound - } - - versions, ok := cnrVersionsMap[objectName] - if !ok { - return nil, ErrNodeNotFound - } - - slices.SortFunc(versions, func(a, b *data.NodeVersion) int { - return cmp.Compare(a.ID, b.ID) - }) - - if len(versions) != 0 { - return versions[len(versions)-1], nil - } - - return nil, ErrNodeNotFound -} - -func (t *TreeServiceMock) GetUnversioned(_ context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) { - cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()] - if !ok { - return nil, ErrNodeNotFound - } - - versions, ok := cnrVersionsMap[objectName] - if !ok { - return nil, ErrNodeNotFound - } - - for _, version := range versions { - if version.IsUnversioned { - return version, nil - } - } - - return nil, ErrNodeNotFound -} - -func (t *TreeServiceMock) AddVersion(_ context.Context, bktInfo *data.BucketInfo, newVersion *data.NodeVersion) (uint64, error) { - cnrVersionsMap, ok := t.versions[bktInfo.CID.EncodeToString()] - if !ok { - t.versions[bktInfo.CID.EncodeToString()] = map[string][]*data.NodeVersion{ - newVersion.FilePath: {newVersion}, - } - return newVersion.ID, nil - } - - versions, ok := cnrVersionsMap[newVersion.FilePath] - if !ok { - cnrVersionsMap[newVersion.FilePath] = []*data.NodeVersion{newVersion} - return newVersion.ID, nil - } - - slices.SortFunc(versions, func(a, b *data.NodeVersion) int { - return cmp.Compare(a.ID, b.ID) - }) - - if len(versions) != 0 { - newVersion.ID = versions[len(versions)-1].ID + 1 - newVersion.Timestamp = versions[len(versions)-1].Timestamp + 1 - } - - result := versions - - if newVersion.IsUnversioned { - result = make([]*data.NodeVersion, 0, len(versions)) - for _, node := range versions { - if !node.IsUnversioned { - result = append(result, node) - } - } - } - - cnrVersionsMap[newVersion.FilePath] = append(result, newVersion) - - return newVersion.ID, nil -} - func (t *TreeServiceMock) CreateMultipartUpload(_ context.Context, bktInfo *data.BucketInfo, info *data.MultipartInfo) (uint64, error) { cnrMultipartsMap, ok := t.multiparts[bktInfo.CID.EncodeToString()] if !ok { diff --git a/api/layer/tree_service.go b/api/layer/tree_service.go index ad4861f0..1220d5e1 100644 --- a/api/layer/tree_service.go +++ b/api/layer/tree_service.go @@ -44,19 +44,10 @@ type TreeService interface { // If object id to remove is not found returns ErrNoNodeToRemove error. DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (oid.ID, error) - GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) - PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error - DeleteObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) error - GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error - GetVersions(ctx context.Context, bktInfo *data.BucketInfo, objectName string) ([]*data.NodeVersion, error) - GetLatestVersion(ctx context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) - GetUnversioned(ctx context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) - AddVersion(ctx context.Context, bktInfo *data.BucketInfo, newVersion *data.NodeVersion) (uint64, error) - CreateMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, info *data.MultipartInfo) (uint64, error) DeleteMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, multipartNodeID uint64) error GetMultipartUploadsByPrefix(ctx context.Context, bktInfo *data.BucketInfo, prefix string) ([]*data.MultipartInfo, error) diff --git a/internal/neofs/tree.go b/internal/neofs/tree.go index d7c8d192..3bc51260 100644 --- a/internal/neofs/tree.go +++ b/internal/neofs/tree.go @@ -18,7 +18,6 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" "github.com/nspcc-dev/neofs-s3-gw/internal/neofs/services/tree" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" - "github.com/nspcc-dev/neofs-sdk-go/user" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -76,9 +75,6 @@ const ( corsFilename = "bucket-cors" bucketTaggingFilename = "bucket-tagging" - // versionTree -- ID of a tree with object versions. - versionTree = "version" - // systemTree -- ID of a tree with system objects // i.e. bucket settings with versioning and lock configuration, cors, notifications. systemTree = "system" @@ -154,54 +150,6 @@ func (n *TreeNode) FileName() (string, bool) { return value, ok } -func newNodeVersion(filePath string, node NodeResponse) (*data.NodeVersion, error) { - treeNode, err := newTreeNode(node) - if err != nil { - return nil, fmt.Errorf("invalid tree node: %w", err) - } - - return newNodeVersionFromTreeNode(filePath, treeNode), nil -} - -func newNodeVersionFromTreeNode(filePath string, treeNode *TreeNode) *data.NodeVersion { - _, isUnversioned := treeNode.Get(isUnversionedKV) - _, isDeleteMarker := treeNode.Get(isDeleteMarkerKV) - eTag, _ := treeNode.Get(etagKV) - - version := &data.NodeVersion{ - BaseNodeVersion: data.BaseNodeVersion{ - ID: treeNode.ID, - ParenID: treeNode.ParentID, - OID: treeNode.ObjID, - Timestamp: treeNode.TimeStamp, - ETag: eTag, - Size: treeNode.Size, - FilePath: filePath, - }, - IsUnversioned: isUnversioned, - } - - if isDeleteMarker { - var created time.Time - if createdStr, ok := treeNode.Get(createdKV); ok { - if utcMilli, err := strconv.ParseInt(createdStr, 10, 64); err == nil { - created = time.UnixMilli(utcMilli) - } - } - - var owner user.ID - if ownerStr, ok := treeNode.Get(ownerKV); ok { - _ = owner.DecodeString(ownerStr) - } - - version.DeleteMarker = &data.DeleteMarkerInfo{ - Created: created, - Owner: owner, - } - } - return version -} - func newMultipartInfo(node NodeResponse) (*data.MultipartInfo, error) { multipartInfo := &data.MultipartInfo{ ID: node.GetNodeId(), @@ -410,66 +358,6 @@ func (c *TreeClient) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketI return oid.ID{}, layer.ErrNoNodeToRemove } -func (c *TreeClient) GetObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) (map[string]string, error) { - tagNode, err := c.getTreeNode(ctx, bktInfo, objVersion.ID, isTagKV) - if err != nil { - return nil, err - } - - return getObjectTagging(tagNode), nil -} - -func getObjectTagging(tagNode *TreeNode) map[string]string { - if tagNode == nil { - return nil - } - - meta := make(map[string]string) - - for key, val := range tagNode.Meta { - if strings.HasPrefix(key, userDefinedTagPrefix) { - meta[strings.TrimPrefix(key, userDefinedTagPrefix)] = val - } - } - - return meta -} - -func (c *TreeClient) PutObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion, tagSet map[string]string) error { - tagNode, err := c.getTreeNode(ctx, bktInfo, objVersion.ID, isTagKV) - if err != nil { - return err - } - - treeTagSet := make(map[string]string) - treeTagSet[isTagKV] = "true" - - for key, val := range tagSet { - treeTagSet[userDefinedTagPrefix+key] = val - } - - if tagNode == nil { - _, err = c.addNode(ctx, bktInfo, versionTree, objVersion.ID, treeTagSet) - } else { - err = c.moveNode(ctx, bktInfo, versionTree, tagNode.ID, objVersion.ID, treeTagSet) - } - - return err -} - -func (c *TreeClient) DeleteObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objVersion *data.NodeVersion) error { - tagNode, err := c.getTreeNode(ctx, bktInfo, objVersion.ID, isTagKV) - if err != nil { - return err - } - - if tagNode == nil { - return nil - } - - return c.removeNode(ctx, bktInfo, versionTree, tagNode.ID) -} - func (c *TreeClient) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) { node, err := c.getSystemNodeWithAllAttributes(ctx, bktInfo, []string{bucketTaggingFilename}) if err != nil { @@ -523,71 +411,6 @@ func (c *TreeClient) DeleteBucketTagging(ctx context.Context, bktInfo *data.Buck return nil } -func (c *TreeClient) getTreeNode(ctx context.Context, bktInfo *data.BucketInfo, nodeID uint64, key string) (*TreeNode, error) { - nodes, err := c.getTreeNodes(ctx, bktInfo, nodeID, key) - if err != nil { - return nil, err - } - // if there will be many allocations, consider having separate - // implementations of 'getTreeNode' and 'getTreeNodes' - return nodes[key], nil -} - -func (c *TreeClient) getTreeNodes(ctx context.Context, bktInfo *data.BucketInfo, nodeID uint64, keys ...string) (map[string]*TreeNode, error) { - subtree, err := c.getSubTree(ctx, bktInfo, versionTree, nodeID, 2) - if err != nil { - return nil, err - } - - treeNodes := make(map[string]*TreeNode, len(keys)) - - for _, s := range subtree { - node, err := newTreeNode(s) - if err != nil { - return nil, err - } - for _, key := range keys { - if _, ok := node.Get(key); ok { - treeNodes[key] = node - break - } - } - if len(treeNodes) == len(keys) { - break - } - } - - return treeNodes, nil -} - -func (c *TreeClient) GetVersions(ctx context.Context, bktInfo *data.BucketInfo, filepath string) ([]*data.NodeVersion, error) { - return c.getVersions(ctx, bktInfo, versionTree, filepath, false) -} - -func (c *TreeClient) GetLatestVersion(ctx context.Context, bktInfo *data.BucketInfo, objectName string) (*data.NodeVersion, error) { - meta := []string{oidKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV} - path := pathFromName(objectName) - - p := &getNodesParams{ - BktInfo: bktInfo, - TreeID: versionTree, - Path: path, - Meta: meta, - LatestOnly: true, - AllAttrs: false, - } - nodes, err := c.getNodes(ctx, p) - if err != nil { - return nil, err - } - - if len(nodes) == 0 { - return nil, layer.ErrNodeNotFound - } - - return newNodeVersion(objectName, nodes[0]) -} - // pathFromName splits name by '/'. func pathFromName(objectName string) []string { return strings.Split(objectName, separator) @@ -711,31 +534,6 @@ func isIntermediate(node NodeResponse) bool { return node.GetMeta()[0].GetKey() == fileNameKV } -func (c *TreeClient) GetUnversioned(ctx context.Context, bktInfo *data.BucketInfo, filepath string) (*data.NodeVersion, error) { - return c.getUnversioned(ctx, bktInfo, versionTree, filepath) -} - -func (c *TreeClient) getUnversioned(ctx context.Context, bktInfo *data.BucketInfo, treeID, filepath string) (*data.NodeVersion, error) { - nodes, err := c.getVersions(ctx, bktInfo, treeID, filepath, true) - if err != nil { - return nil, err - } - - if len(nodes) > 1 { - return nil, fmt.Errorf("found more than one unversioned node") - } - - if len(nodes) != 1 { - return nil, layer.ErrNodeNotFound - } - - return nodes[0], nil -} - -func (c *TreeClient) AddVersion(ctx context.Context, bktInfo *data.BucketInfo, version *data.NodeVersion) (uint64, error) { - return c.addVersion(ctx, bktInfo, versionTree, version) -} - func (c *TreeClient) CreateMultipartUpload(ctx context.Context, bktInfo *data.BucketInfo, info *data.MultipartInfo) (uint64, error) { path := pathFromName(info.Key) meta := metaFromMultipart(info, path[len(path)-1]) @@ -969,94 +767,6 @@ func (c *TreeClient) Close() error { return nil } -func (c *TreeClient) addVersion(ctx context.Context, bktInfo *data.BucketInfo, treeID string, version *data.NodeVersion) (uint64, error) { - path := pathFromName(version.FilePath) - meta := map[string]string{ - oidKV: version.OID.EncodeToString(), - fileNameKV: path[len(path)-1], - } - - if version.Size > 0 { - meta[sizeKV] = strconv.FormatInt(version.Size, 10) - } - if len(version.ETag) > 0 { - meta[etagKV] = version.ETag - } - - if version.IsDeleteMarker() { - meta[isDeleteMarkerKV] = "true" - meta[ownerKV] = version.DeleteMarker.Owner.EncodeToString() - meta[createdKV] = strconv.FormatInt(version.DeleteMarker.Created.UTC().UnixMilli(), 10) - } - - if version.IsUnversioned { - meta[isUnversionedKV] = "true" - - node, err := c.getUnversioned(ctx, bktInfo, treeID, version.FilePath) - if err == nil { - if err = c.moveNode(ctx, bktInfo, treeID, node.ID, node.ParenID, meta); err != nil { - return 0, err - } - - return node.ID, c.clearOutdatedVersionInfo(ctx, bktInfo, treeID, node.ID) - } - - if !errors.Is(err, layer.ErrNodeNotFound) { - return 0, err - } - } - - return c.addNodeByPath(ctx, bktInfo, treeID, path[:len(path)-1], meta) -} - -func (c *TreeClient) clearOutdatedVersionInfo(ctx context.Context, bktInfo *data.BucketInfo, treeID string, nodeID uint64) error { - taggingNode, err := c.getTreeNode(ctx, bktInfo, nodeID, isTagKV) - if err != nil { - return err - } - if taggingNode != nil { - return c.removeNode(ctx, bktInfo, treeID, taggingNode.ID) - } - - return nil -} - -func (c *TreeClient) getVersions(ctx context.Context, bktInfo *data.BucketInfo, treeID, filepath string, onlyUnversioned bool) ([]*data.NodeVersion, error) { - keysToReturn := []string{oidKV, isUnversionedKV, isDeleteMarkerKV, etagKV, sizeKV} - path := pathFromName(filepath) - p := &getNodesParams{ - BktInfo: bktInfo, - TreeID: treeID, - Path: path, - Meta: keysToReturn, - LatestOnly: false, - AllAttrs: false, - } - nodes, err := c.getNodes(ctx, p) - if err != nil { - if errors.Is(err, layer.ErrNodeNotFound) { - return nil, nil - } - return nil, err - } - - result := make([]*data.NodeVersion, 0, len(nodes)) - for _, node := range nodes { - nodeVersion, err := newNodeVersion(filepath, node) - if err != nil { - return nil, err - } - - if onlyUnversioned && !nodeVersion.IsUnversioned { - continue - } - - result = append(result, nodeVersion) - } - - return result, nil -} - func (c *TreeClient) getSubTree(ctx context.Context, bktInfo *data.BucketInfo, treeID string, rootID uint64, depth uint32) ([]*tree.GetSubTreeResponse_Body, error) { request := &tree.GetSubTreeRequest{ Body: &tree.GetSubTreeRequest_Body{