From 99feb1d9363075040d90b40542fdc4b73ca83229 Mon Sep 17 00:00:00 2001 From: Angira Kekteeva Date: Tue, 24 May 2022 09:58:33 +0300 Subject: [PATCH] [#449] Add tree service for object tagging Signed-off-by: Angira Kekteeva --- api/cache/system.go | 18 +++ api/data/info.go | 3 - api/data/tree.go | 7 ++ api/handler/get.go | 8 +- api/handler/head.go | 8 +- api/handler/multipart_upload.go | 9 +- api/handler/put.go | 15 ++- api/handler/tagging.go | 66 ++++------- api/layer/layer.go | 112 +++--------------- api/layer/tagging.go | 169 +++++++++++++++++++++++++++ api/layer/tree_service.go | 4 + internal/neofs/tree.go | 70 +++++++++++ internal/neofstest/tree/tree_mock.go | 30 +++++ 13 files changed, 367 insertions(+), 152 deletions(-) create mode 100644 api/layer/tagging.go diff --git a/api/cache/system.go b/api/cache/system.go index ce025aa2..afd4de92 100644 --- a/api/cache/system.go +++ b/api/cache/system.go @@ -104,6 +104,20 @@ func (o *SystemCache) GetNotificationConfiguration(key string) *data.Notificatio return result } +func (o *SystemCache) GetObjectTagging(key string) map[string]string { + entry, err := o.cache.Get(key) + if err != nil { + return nil + } + + result, ok := entry.(map[string]string) + if !ok { + return nil + } + + return result +} + // PutObject puts an object to cache. func (o *SystemCache) PutObject(key string, obj *data.ObjectInfo) error { return o.cache.Set(key, obj) @@ -121,6 +135,10 @@ func (o *SystemCache) PutNotificationConfiguration(key string, obj *data.Notific return o.cache.Set(key, obj) } +func (o *SystemCache) PutObjectTagging(key string, tagSet map[string]string) error { + return o.cache.Set(key, tagSet) +} + // Delete deletes an object from cache. func (o *SystemCache) Delete(key string) bool { return o.cache.Remove(key) diff --git a/api/data/info.go b/api/data/info.go index 99f644e0..a45aab1b 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -102,9 +102,6 @@ func (o *ObjectInfo) Address() oid.Address { return addr } -// TagsObject returns the name of a system object for tags. -func (o *ObjectInfo) TagsObject() string { return ".tagset." + o.Name + "." + o.Version() } - // LegalHoldObject returns the name of a system object for a lock object. func (o *ObjectInfo) LegalHoldObject() string { return ".lock." + o.Name + "." + o.Version() } diff --git a/api/data/tree.go b/api/data/tree.go index 446286be..f2312a8f 100644 --- a/api/data/tree.go +++ b/api/data/tree.go @@ -3,6 +3,7 @@ package data import ( "time" + 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" ) @@ -35,3 +36,9 @@ type BaseNodeVersion struct { OID oid.ID Timestamp uint64 } + +type ObjectTaggingInfo struct { + CnrID *cid.ID + ObjName string + VersionID string +} diff --git a/api/handler/get.go b/api/handler/get.go index 95804719..140a2f8d 100644 --- a/api/handler/get.go +++ b/api/handler/get.go @@ -138,7 +138,13 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { return } - tagSet, err := h.obj.GetObjectTagging(r.Context(), info) + t := &data.ObjectTaggingInfo{ + CnrID: &info.CID, + ObjName: info.Name, + VersionID: info.Version(), + } + + tagSet, err := h.obj.GetObjectTagging(r.Context(), t) if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) { h.logAndSendError(w, "could not get object tag set", reqInfo, err) return diff --git a/api/handler/head.go b/api/handler/head.go index f4bfaf4d..762dead9 100644 --- a/api/handler/head.go +++ b/api/handler/head.go @@ -62,7 +62,13 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { return } - tagSet, err := h.obj.GetObjectTagging(r.Context(), info) + t := &data.ObjectTaggingInfo{ + CnrID: &info.CID, + ObjName: info.Name, + VersionID: info.Version(), + } + + tagSet, err := h.obj.GetObjectTagging(r.Context(), t) if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) { h.logAndSendError(w, "could not get object tag set", reqInfo, err) return diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index 43de1c39..ed1f2f0d 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -412,11 +412,12 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http. } if len(uploadData.TagSet) != 0 { - t := &layer.PutTaggingParams{ - ObjectInfo: objInfo, - TagSet: uploadData.TagSet, + t := &data.ObjectTaggingInfo{ + CnrID: &bktInfo.CID, + ObjName: objInfo.Name, + VersionID: objInfo.Version(), } - if err = h.obj.PutObjectTagging(r.Context(), t); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), t, uploadData.TagSet); 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 04e22aad..f8410d47 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -252,8 +252,13 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { } } + t := &data.ObjectTaggingInfo{ + CnrID: &info.CID, + ObjName: info.Name, + VersionID: info.Version(), + } if tagSet != nil { - if err = h.obj.PutObjectTagging(r.Context(), &layer.PutTaggingParams{ObjectInfo: info, TagSet: tagSet}); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), t, tagSet); err != nil { h.logAndSendError(w, "could not upload object tagging", reqInfo, err) return } @@ -374,8 +379,14 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { } } + t := &data.ObjectTaggingInfo{ + CnrID: &info.CID, + ObjName: info.Name, + VersionID: info.Version(), + } + if tagSet != nil { - if err = h.obj.PutObjectTagging(r.Context(), &layer.PutTaggingParams{ObjectInfo: info, TagSet: tagSet}); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), t, tagSet); 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 1c35e80a..23671c91 100644 --- a/api/handler/tagging.go +++ b/api/handler/tagging.go @@ -9,8 +9,8 @@ import ( "unicode" "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/errors" - "github.com/nspcc-dev/neofs-s3-gw/api/layer" "go.uber.org/zap" ) @@ -37,31 +37,22 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request return } - p := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, + p := &data.ObjectTaggingInfo{ + CnrID: &bktInfo.CID, + ObjName: reqInfo.ObjectName, VersionID: reqInfo.URL.Query().Get("versionId"), } - objInfo, err := h.obj.GetObjectInfo(r.Context(), p) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - p2 := &layer.PutTaggingParams{ - ObjectInfo: objInfo, - TagSet: tagSet, - } - - if err = h.obj.PutObjectTagging(r.Context(), p2); err != nil { + if err = h.obj.PutObjectTagging(r.Context(), p, tagSet); err != nil { h.logAndSendError(w, "could not put object tagging", reqInfo, err) return } s := &SendNotificationParams{ - Event: EventObjectTaggingPut, - ObjInfo: objInfo, + Event: EventObjectTaggingPut, + ObjInfo: &data.ObjectInfo{ + Name: reqInfo.ObjectName, + }, BktInfo: bktInfo, ReqInfo: reqInfo, } @@ -74,6 +65,7 @@ func (h *handler) PutObjectTaggingHandler(w http.ResponseWriter, r *http.Request func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) { reqInfo := api.GetReqInfo(r.Context()) + versionID := reqInfo.URL.Query().Get("versionId") bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { @@ -81,25 +73,19 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request return } - p := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, - VersionID: reqInfo.URL.Query().Get("versionId"), + p := &data.ObjectTaggingInfo{ + CnrID: &bktInfo.CID, + ObjName: reqInfo.ObjectName, + VersionID: versionID, } - objInfo, err := h.obj.GetObjectInfo(r.Context(), p) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - tagSet, err := h.obj.GetObjectTagging(r.Context(), objInfo) + tagSet, err := h.obj.GetObjectTagging(r.Context(), p) if err != nil { h.logAndSendError(w, "could not get object tagging", reqInfo, err) return } - w.Header().Set(api.AmzVersionID, objInfo.Version()) + w.Header().Set(api.AmzVersionID, versionID) if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil { h.logAndSendError(w, "something went wrong", reqInfo, err) } @@ -114,26 +100,22 @@ func (h *handler) DeleteObjectTaggingHandler(w http.ResponseWriter, r *http.Requ return } - p := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, + p := &data.ObjectTaggingInfo{ + CnrID: &bktInfo.CID, + ObjName: reqInfo.ObjectName, VersionID: reqInfo.URL.Query().Get("versionId"), } - objInfo, err := h.obj.GetObjectInfo(r.Context(), p) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - if err = h.obj.DeleteObjectTagging(r.Context(), bktInfo, objInfo); err != nil { + if err = h.obj.DeleteObjectTagging(r.Context(), p); err != nil { h.logAndSendError(w, "could not delete object tagging", reqInfo, err) return } s := &SendNotificationParams{ - Event: EventObjectTaggingDelete, - ObjInfo: objInfo, + Event: EventObjectTaggingDelete, + ObjInfo: &data.ObjectInfo{ + Name: reqInfo.ObjectName, + }, BktInfo: bktInfo, ReqInfo: reqInfo, } diff --git a/api/layer/layer.go b/api/layer/layer.go index 09f14ef4..ea69c71a 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "net/url" - "strings" "time" "github.com/nats-io/nats.go" @@ -189,12 +188,6 @@ type ( Error error } - // PutTaggingParams stores tag set params. - PutTaggingParams struct { - ObjectInfo *data.ObjectInfo - TagSet map[string]string - } - // Client provides S3 API client interface. Client interface { Initialize(ctx context.Context, c EventListener) error @@ -217,13 +210,17 @@ type ( GetObject(ctx context.Context, p *GetObjectParams) error HeadSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) (*data.ObjectInfo, error) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.ObjectInfo, error) - GetObjectTagging(ctx context.Context, p *data.ObjectInfo) (map[string]string, 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 + + GetObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) (map[string]string, error) + PutObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo, tagSet map[string]string) error + DeleteObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) error PutObject(ctx context.Context, p *PutObjectParams) (*data.ObjectInfo, error) PutSystemObject(ctx context.Context, p *PutSystemObjectParams) (*data.ObjectInfo, error) - PutObjectTagging(ctx context.Context, p *PutTaggingParams) error - PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error CopyObject(ctx context.Context, p *CopyObjectParams) (*data.ObjectInfo, error) @@ -233,8 +230,6 @@ type ( DeleteObjects(ctx context.Context, p *DeleteObjectParams) ([]*VersionedObject, error) DeleteSystemObject(ctx context.Context, bktInfo *data.BucketInfo, name string) error - DeleteObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) error - DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error CompleteMultipartUpload(ctx context.Context, p *CompleteMultipartParams) (*data.ObjectInfo, error) UploadPart(ctx context.Context, p *UploadPartParams) (*data.ObjectInfo, error) @@ -430,92 +425,6 @@ func (n *layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*data.O return n.headVersion(ctx, p.BktInfo, p) } -// GetObjectTagging from storage. -func (n *layer) GetObjectTagging(ctx context.Context, oi *data.ObjectInfo) (map[string]string, error) { - bktInfo := &data.BucketInfo{ - Name: oi.Bucket, - CID: oi.CID, - Owner: oi.Owner, - } - - objInfo, err := n.HeadSystemObject(ctx, bktInfo, oi.TagsObject()) - if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) { - return nil, err - } - - return formTagSet(objInfo), nil -} - -// GetBucketTagging from storage. -func (n *layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) { - objInfo, err := n.HeadSystemObject(ctx, bktInfo, formBucketTagObjectName(bktInfo.Name)) - if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) { - return nil, err - } - - return formTagSet(objInfo), nil -} - -func formTagSet(objInfo *data.ObjectInfo) map[string]string { - var tagSet map[string]string - if objInfo != nil { - tagSet = make(map[string]string, len(objInfo.Headers)) - for k, v := range objInfo.Headers { - if strings.HasPrefix(k, tagPrefix) { - if v == tagEmptyMark { - v = "" - } - tagSet[strings.TrimPrefix(k, tagPrefix)] = v - } - } - } - return tagSet -} - -// PutObjectTagging into storage. -func (n *layer) PutObjectTagging(ctx context.Context, p *PutTaggingParams) error { - bktInfo := &data.BucketInfo{ - Name: p.ObjectInfo.Bucket, - CID: p.ObjectInfo.CID, - Owner: p.ObjectInfo.Owner, - } - - s := &PutSystemObjectParams{ - BktInfo: bktInfo, - ObjName: p.ObjectInfo.TagsObject(), - Metadata: p.TagSet, - Prefix: tagPrefix, - Reader: nil, - } - - _, err := n.PutSystemObject(ctx, s) - return err -} - -// PutBucketTagging into storage. -func (n *layer) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error { - s := &PutSystemObjectParams{ - BktInfo: bktInfo, - ObjName: formBucketTagObjectName(bktInfo.Name), - Metadata: tagSet, - Prefix: tagPrefix, - Reader: nil, - } - - _, err := n.PutSystemObject(ctx, s) - return err -} - -// DeleteObjectTagging from storage. -func (n *layer) DeleteObjectTagging(ctx context.Context, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) error { - return n.DeleteSystemObject(ctx, bktInfo, objInfo.TagsObject()) -} - -// DeleteBucketTagging from storage. -func (n *layer) DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error { - return n.DeleteSystemObject(ctx, bktInfo, formBucketTagObjectName(bktInfo.Name)) -} - // CopyObject from one bucket into another bucket. func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*data.ObjectInfo, error) { pr, pw := io.Pipe() @@ -618,7 +527,12 @@ func (n *layer) removeVersionIfFound(ctx context.Context, bkt *data.BucketInfo, return deleteMarkVersion, err } - return deleteMarkVersion, n.DeleteObjectTagging(ctx, bkt, &data.ObjectInfo{ID: version.OID, Bucket: bkt.Name, Name: obj.Name}) + p := &data.ObjectTaggingInfo{ + CnrID: &bkt.CID, + ObjName: obj.Name, + VersionID: version.OID.EncodeToString(), + } + return deleteMarkVersion, n.DeleteObjectTagging(ctx, p) } return "", errors.GetAPIError(errors.ErrNoSuchVersion) diff --git a/api/layer/tagging.go b/api/layer/tagging.go new file mode 100644 index 00000000..bd392e59 --- /dev/null +++ b/api/layer/tagging.go @@ -0,0 +1,169 @@ +package layer + +import ( + "context" + errorsStd "errors" + "strings" + + "go.uber.org/zap" + + "github.com/nspcc-dev/neofs-s3-gw/api/data" + "github.com/nspcc-dev/neofs-s3-gw/api/errors" +) + +func (n *layer) GetObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) (map[string]string, error) { + var ( + err error + tags map[string]string + ) + tags = n.systemCache.GetObjectTagging(objectTaggingCacheKey(p)) + if tags != nil { + return tags, nil + } + + version, err := n.getTaggedObjectVersion(ctx, p) + if err != nil { + return nil, err + } + + tags, err = n.treeService.GetObjectTagging(ctx, p.CnrID, version) + if err != nil { + if errorsStd.Is(err, ErrNodeNotFound) { + return nil, errors.GetAPIError(errors.ErrNoSuchKey) + } + return nil, err + } + + if err = n.systemCache.PutObjectTagging(objectTaggingCacheKey(p), tags); err != nil { + n.log.Error("couldn't cache system object", zap.Error(err)) + } + + return tags, nil +} + +func (n *layer) PutObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo, tagSet map[string]string) error { + version, err := n.getTaggedObjectVersion(ctx, p) + if err != nil { + return err + } + + err = n.treeService.PutObjectTagging(ctx, p.CnrID, version, tagSet) + if err != nil { + if errorsStd.Is(err, ErrNodeNotFound) { + return errors.GetAPIError(errors.ErrNoSuchKey) + } + return err + } + + if err = n.systemCache.PutObjectTagging(objectTaggingCacheKey(p), tagSet); err != nil { + n.log.Error("couldn't cache system object", zap.Error(err)) + } + + return nil +} + +func (n *layer) DeleteObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) error { + version, err := n.getTaggedObjectVersion(ctx, p) + if err != nil { + return err + } + + err = n.treeService.DeleteObjectTagging(ctx, p.CnrID, version) + if err != nil { + if errorsStd.Is(err, ErrNodeNotFound) { + return errors.GetAPIError(errors.ErrNoSuchKey) + } + return err + } + + n.systemCache.Delete(objectTaggingCacheKey(p)) + + return nil +} + +func (n *layer) GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error) { + objInfo, err := n.HeadSystemObject(ctx, bktInfo, formBucketTagObjectName(bktInfo.Name)) + if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchKey) { + return nil, err + } + + return formTagSet(objInfo), nil +} + +func (n *layer) PutBucketTagging(ctx context.Context, bktInfo *data.BucketInfo, tagSet map[string]string) error { + s := &PutSystemObjectParams{ + BktInfo: bktInfo, + ObjName: formBucketTagObjectName(bktInfo.Name), + Metadata: tagSet, + Prefix: tagPrefix, + Reader: nil, + } + + _, err := n.PutSystemObject(ctx, s) + return err +} + +func (n *layer) DeleteBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) error { + return n.DeleteSystemObject(ctx, bktInfo, formBucketTagObjectName(bktInfo.Name)) +} + +func formTagSet(objInfo *data.ObjectInfo) map[string]string { + var tagSet map[string]string + if objInfo != nil { + tagSet = make(map[string]string, len(objInfo.Headers)) + for k, v := range objInfo.Headers { + if strings.HasPrefix(k, tagPrefix) { + if v == tagEmptyMark { + v = "" + } + tagSet[strings.TrimPrefix(k, tagPrefix)] = v + } + } + } + + return tagSet +} + +func objectTaggingCacheKey(p *data.ObjectTaggingInfo) string { + return ".tagset." + p.CnrID.EncodeToString() + "." + p.ObjName + "." + p.VersionID +} + +func (n *layer) getTaggedObjectVersion(ctx context.Context, p *data.ObjectTaggingInfo) (*data.NodeVersion, error) { + var ( + err error + version *data.NodeVersion + ) + + if p.VersionID == "null" { + if version, err = n.treeService.GetUnversioned(ctx, p.CnrID, p.ObjName); err != nil { + if strings.Contains(err.Error(), "not found") { + return nil, errors.GetAPIError(errors.ErrNoSuchKey) + } + return nil, err + } + } else if len(p.VersionID) == 0 { + if version, err = n.treeService.GetLatestVersion(ctx, p.CnrID, p.ObjName); err != nil { + if strings.Contains(err.Error(), "not found") { + return nil, errors.GetAPIError(errors.ErrNoSuchKey) + } + return nil, err + } + } else { + versions, err := n.treeService.GetVersions(ctx, p.CnrID, p.ObjName) + if err != nil { + return nil, err + } + for _, v := range versions { + if v.OID.EncodeToString() == p.VersionID { + version = v + break + } + } + } + + if version == nil || version.DeleteMarker != nil { + return nil, errors.GetAPIError(errors.ErrNoSuchKey) + } + + return version, nil +} diff --git a/api/layer/tree_service.go b/api/layer/tree_service.go index a72f19e3..f0bb81cf 100644 --- a/api/layer/tree_service.go +++ b/api/layer/tree_service.go @@ -30,6 +30,10 @@ type TreeService interface { // DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in NeoFS DeleteBucketCORS(ctx context.Context, cnrID *cid.ID) (*oid.ID, error) + GetObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) (map[string]string, error) + PutObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion, tagSet map[string]string) error + DeleteObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) error + GetVersions(ctx context.Context, cnrID *cid.ID, objectName string) ([]*data.NodeVersion, error) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) GetLatestVersionsByPrefix(ctx context.Context, cnrID *cid.ID, prefix string) ([]oid.ID, error) diff --git a/internal/neofs/tree.go b/internal/neofs/tree.go index d0c8a0b5..4c0f4670 100644 --- a/internal/neofs/tree.go +++ b/internal/neofs/tree.go @@ -44,6 +44,7 @@ const ( fileNameKV = "FileName" systemNameKV = "SystemName" isUnversionedKV = "IsUnversioned" + isTagKV = "isTag" // keys for delete marker nodes isDeleteMarkerKV = "IdDeleteMarker" @@ -274,6 +275,75 @@ func (c *TreeClient) DeleteBucketCORS(ctx context.Context, cnrID *cid.ID) (*oid. return nil, nil } +func (c *TreeClient) GetObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) (map[string]string, error) { + tagNode, err := c.getObjectTaggingNode(ctx, cnrID, objVersion) + if err != nil { + return nil, err + } + + if tagNode == nil { + return nil, nil + } + + delete(tagNode.Meta, isTagKV) + + return tagNode.Meta, nil +} + +func (c *TreeClient) PutObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion, tagSet map[string]string) error { + tagNode, err := c.getObjectTaggingNode(ctx, cnrID, objVersion) + if err != nil { + return err + } + + tagSet[isTagKV] = "true" + + if tagNode == nil { + _, err = c.addNode(ctx, cnrID, versionTree, objVersion.ID, tagSet) + } else { + err = c.moveNode(ctx, cnrID, versionTree, tagNode.ID, objVersion.ID, tagSet) + } + + delete(tagSet, isTagKV) + + return err +} + +func (c *TreeClient) DeleteObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) error { + tagNode, err := c.getObjectTaggingNode(ctx, cnrID, objVersion) + if err != nil { + return err + } + + if tagNode == nil { + return nil + } + + return c.removeNode(ctx, cnrID, versionTree, tagNode.ID) +} + +func (c *TreeClient) getObjectTaggingNode(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) (*TreeNode, error) { + subtree, err := c.getSubTree(ctx, cnrID, versionTree, objVersion.ID, 1) + if err != nil { + return nil, err + } + + var tagNode *TreeNode + + for _, s := range subtree { + node, err := newTreeNode(s) + if err != nil { + return nil, err + } + if _, ok := node.Get(isTagKV); ok { + tagNode = node + break + } + } + + return tagNode, nil +} + func (c *TreeClient) GetVersions(ctx context.Context, cnrID *cid.ID, filepath string) ([]*data.NodeVersion, error) { return c.getVersions(ctx, cnrID, versionTree, filepath, false) } diff --git a/internal/neofstest/tree/tree_mock.go b/internal/neofstest/tree/tree_mock.go index a32a7562..0d0ce7be 100644 --- a/internal/neofstest/tree/tree_mock.go +++ b/internal/neofstest/tree/tree_mock.go @@ -17,6 +17,21 @@ type TreeServiceMock struct { system map[string]map[string]*data.BaseNodeVersion } +func (t *TreeServiceMock) GetObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) (map[string]string, error) { + //TODO implement me + panic("implement me") +} + +func (t *TreeServiceMock) PutObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion, tagSet map[string]string) error { + //TODO implement me + panic("implement me") +} + +func (t *TreeServiceMock) DeleteObjectTagging(ctx context.Context, cnrID *cid.ID, objVersion *data.NodeVersion) error { + //TODO implement me + panic("implement me") +} + var ErrNodeNotFound = errors.New("not found") func NewTreeService() *TreeServiceMock { @@ -205,3 +220,18 @@ func (t *TreeServiceMock) RemoveSystemVersion(ctx context.Context, cnrID *cid.ID func (t *TreeServiceMock) GetAllVersionsByPrefix(ctx context.Context, cnrID *cid.ID, prefix string) ([]*data.NodeVersion, error) { panic("implement me") } + +func (t *TreeServiceMock) GetObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) (map[string]string, error) { + //TODO implement me + panic("implement me") +} + +func (t *TreeServiceMock) PutObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) error { + //TODO implement me + panic("implement me") +} + +func (t *TreeServiceMock) DeleteObjectTagging(ctx context.Context, p *data.ObjectTaggingInfo) error { + //TODO implement me + panic("implement me") +}