diff --git a/api/data/tree.go b/api/data/tree.go index ecfe10c8..2fb8b460 100644 --- a/api/data/tree.go +++ b/api/data/tree.go @@ -75,9 +75,61 @@ func (p *PartInfo) ToHeaderString() string { // LockInfo is lock information to create appropriate tree node. type LockInfo struct { - ID uint64 - LegalHoldOID *oid.ID - RetentionOID *oid.ID - UntilDate string - IsCompliance bool + id uint64 + + legalHoldOID oid.ID + setLegalHold bool + + retentionOID oid.ID + setRetention bool + untilDate string + isCompliance bool +} + +func NewLockInfo(id uint64) *LockInfo { + return &LockInfo{id: id} +} + +func (l LockInfo) ID() uint64 { + return l.id +} + +func (l *LockInfo) SetLegalHold(objID oid.ID) { + l.legalHoldOID = objID + l.setLegalHold = true +} + +func (l *LockInfo) ResetLegalHold() { + l.setLegalHold = false +} + +func (l LockInfo) LegalHold() oid.ID { + return l.legalHoldOID +} + +func (l LockInfo) IsLegalHoldSet() bool { + return l.setLegalHold +} + +func (l *LockInfo) SetRetention(objID oid.ID, until string, isCompliance bool) { + l.retentionOID = objID + l.setRetention = true + l.untilDate = until + l.isCompliance = isCompliance +} + +func (l LockInfo) IsRetentionSet() bool { + return l.setRetention +} + +func (l LockInfo) Retention() oid.ID { + return l.retentionOID +} + +func (l LockInfo) UntilDate() string { + return l.untilDate +} + +func (l LockInfo) IsCompliance() bool { + return l.isCompliance } diff --git a/api/handler/head.go b/api/handler/head.go index 12a62024..fb980ebe 100644 --- a/api/handler/head.go +++ b/api/handler/head.go @@ -120,12 +120,12 @@ func (h *handler) setLockingHeaders(bktInfo *data.BucketInfo, lockInfo *data.Loc legalHold := &data.LegalHold{Status: legalHoldOff} retention := &data.Retention{Mode: governanceMode} - if lockInfo.LegalHoldOID != nil { + if lockInfo.IsLegalHoldSet() { legalHold.Status = legalHoldOn } - if lockInfo.RetentionOID != nil { - retention.RetainUntilDate = lockInfo.UntilDate - if lockInfo.IsCompliance { + if lockInfo.IsRetentionSet() { + retention.RetainUntilDate = lockInfo.UntilDate() + if lockInfo.IsCompliance() { retention.Mode = complianceMode } } diff --git a/api/handler/locking.go b/api/handler/locking.go index c9c31ce0..2dd817d2 100644 --- a/api/handler/locking.go +++ b/api/handler/locking.go @@ -174,7 +174,7 @@ func (h *handler) GetObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque } legalHold := &data.LegalHold{Status: legalHoldOff} - if lockInfo.LegalHoldOID != nil { + if lockInfo.IsLegalHoldSet() { legalHold.Status = legalHoldOn } @@ -248,16 +248,16 @@ func (h *handler) GetObjectRetentionHandler(w http.ResponseWriter, r *http.Reque return } - if lockInfo.RetentionOID == nil { + if !lockInfo.IsRetentionSet() { h.logAndSendError(w, "retention lock isn't set", reqInfo, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)) return } retention := &data.Retention{ Mode: governanceMode, - RetainUntilDate: lockInfo.UntilDate, + RetainUntilDate: lockInfo.UntilDate(), } - if lockInfo.IsCompliance { + if lockInfo.IsCompliance() { retention.Mode = complianceMode } diff --git a/api/layer/cors.go b/api/layer/cors.go index b22f908b..895abda6 100644 --- a/api/layer/cors.go +++ b/api/layer/cors.go @@ -49,12 +49,13 @@ func (n *layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error { } objIDToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo.CID, objID) - if err != nil { + objIDToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove) + if err != nil && !objIDToDeleteNotFound { return err } - if objIDToDelete != nil { - if err = n.objectDelete(ctx, p.BktInfo, *objIDToDelete); err != nil { + if !objIDToDeleteNotFound { + if err = n.objectDelete(ctx, p.BktInfo, objIDToDelete); err != nil { n.log.Error("couldn't delete cors object", zap.Error(err), zap.String("cnrID", p.BktInfo.CID.EncodeToString()), zap.String("bucket name", p.BktInfo.Name), @@ -83,11 +84,12 @@ func (n *layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*d func (n *layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error { objID, err := n.treeService.DeleteBucketCORS(ctx, bktInfo.CID) - if err != nil { + objIDNotFound := errorsStd.Is(err, ErrNoNodeToRemove) + if err != nil && !objIDNotFound { return err } - if objID != nil { - if err = n.objectDelete(ctx, bktInfo, *objID); err != nil { + if !objIDNotFound { + if err = n.objectDelete(ctx, bktInfo, objID); err != nil { return err } } diff --git a/api/layer/layer.go b/api/layer/layer.go index 3f762576..bdb42d33 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -462,15 +462,15 @@ func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*data.Obje }) } -func getRandomOID() (*oid.ID, error) { +func getRandomOID() (oid.ID, error) { b := [32]byte{} if _, err := rand.Read(b[:]); err != nil { - return nil, err + return oid.ID{}, err } var objID oid.ID objID.SetSHA256(b) - return &objID, nil + return objID, nil } // DeleteObject removes all objects with the passed nice name. @@ -505,7 +505,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, obj *Ver newVersion := &data.NodeVersion{ BaseNodeVersion: data.BaseNodeVersion{ - OID: *randOID, + OID: randOID, FilePath: obj.Name, }, DeleteMarker: &data.DeleteMarkerInfo{ diff --git a/api/layer/locking_test.go b/api/layer/locking_test.go index edd0ab91..f5abdd55 100644 --- a/api/layer/locking_test.go +++ b/api/layer/locking_test.go @@ -36,7 +36,7 @@ func TestObjectLockAttributes(t *testing.T) { foundLock, err := tc.layer.GetLockInfo(tc.ctx, p) require.NoError(t, err) - lockObj := tc.getObjectByID(*foundLock.RetentionOID) + lockObj := tc.getObjectByID(foundLock.Retention()) require.NotNil(t, lockObj) expEpoch := false diff --git a/api/layer/multipart_upload.go b/api/layer/multipart_upload.go index 24e9de18..ddc42c7d 100644 --- a/api/layer/multipart_upload.go +++ b/api/layer/multipart_upload.go @@ -198,18 +198,19 @@ func (n *layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf Key: p.Info.Key, UploadID: p.Info.UploadID, Number: p.PartNumber, - OID: *id, + OID: id, Size: p.Size, ETag: hex.EncodeToString(hash), Created: time.Now(), } oldPartID, err := n.treeService.AddPart(ctx, bktInfo.CID, multipartInfo.ID, partInfo) - if err != nil { + oldPartIDNotFound := stderrors.Is(err, ErrNoNodeToRemove) + if err != nil && !oldPartIDNotFound { return nil, err } - if oldPartID != nil { - if err = n.objectDelete(ctx, bktInfo, *oldPartID); err != nil { + if !oldPartIDNotFound { + if err = n.objectDelete(ctx, bktInfo, oldPartID); err != nil { n.log.Error("couldn't delete old part object", zap.Error(err), zap.String("cnrID", bktInfo.CID.EncodeToString()), zap.String("bucket name", bktInfo.Name), @@ -218,7 +219,7 @@ func (n *layer) uploadPart(ctx context.Context, multipartInfo *data.MultipartInf } objInfo := &data.ObjectInfo{ - ID: *id, + ID: id, CID: bktInfo.CID, Owner: bktInfo.Owner, diff --git a/api/layer/neofs.go b/api/layer/neofs.go index 8eaa9233..0d165522 100644 --- a/api/layer/neofs.go +++ b/api/layer/neofs.go @@ -146,7 +146,7 @@ type NeoFS interface { // // Created container is public with enabled ACL extension. // - // It returns exactly one non-nil value. It returns any error encountered which + // It returns exactly one non-zero value. It returns any error encountered which // prevented the container from being created. CreateContainer(context.Context, PrmContainerCreate) (cid.ID, error) @@ -214,9 +214,9 @@ type NeoFS interface { // // It returns ErrAccessDenied on write access violation. // - // It returns exactly one non-nil value. It returns any error encountered which + // It returns exactly one non-zero value. It returns any error encountered which // prevented the container from being created. - CreateObject(context.Context, PrmObjectCreate) (*oid.ID, error) + CreateObject(context.Context, PrmObjectCreate) (oid.ID, error) // DeleteObject marks the object to be removed from the NeoFS container by identifier. // Successful return does not guarantee actual removal. diff --git a/api/layer/neofs_mock.go b/api/layer/neofs_mock.go index d7feca82..eba4cf38 100644 --- a/api/layer/neofs_mock.go +++ b/api/layer/neofs_mock.go @@ -178,10 +178,10 @@ func (t *TestNeoFS) ReadObject(_ context.Context, prm PrmObjectRead) (*ObjectPar return nil, fmt.Errorf("object not found %s", addr) } -func (t *TestNeoFS) CreateObject(_ context.Context, prm PrmObjectCreate) (*oid.ID, error) { +func (t *TestNeoFS) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) { b := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, b); err != nil { - return nil, err + return oid.ID{}, err } var id oid.ID id.SetSHA256(sha256.Sum256(b)) @@ -219,7 +219,7 @@ func (t *TestNeoFS) CreateObject(_ context.Context, prm PrmObjectCreate) (*oid.I if prm.Payload != nil { all, err := io.ReadAll(prm.Payload) if err != nil { - return nil, err + return oid.ID{}, err } obj.SetPayload(all) obj.SetPayloadSize(uint64(len(all))) @@ -233,7 +233,7 @@ func (t *TestNeoFS) CreateObject(_ context.Context, prm PrmObjectCreate) (*oid.I addr := newAddress(cnrID, objID) t.objects[addr.EncodeToString()] = obj - return &objID, nil + return objID, nil } func (t *TestNeoFS) DeleteObject(_ context.Context, prm PrmObjectDelete) error { diff --git a/api/layer/notifications.go b/api/layer/notifications.go index 99d309e1..0ecaadd6 100644 --- a/api/layer/notifications.go +++ b/api/layer/notifications.go @@ -39,12 +39,13 @@ func (n *layer) PutBucketNotificationConfiguration(ctx context.Context, p *PutBu } objIDToDelete, err := n.treeService.PutNotificationConfigurationNode(ctx, p.BktInfo.CID, objID) - if err != nil { + objIDToDeleteNotFound := errorsStd.Is(err, ErrNoNodeToRemove) + if err != nil && !objIDToDeleteNotFound { return err } - if objIDToDelete != nil { - if err = n.objectDelete(ctx, p.BktInfo, *objIDToDelete); err != nil { + if !objIDToDeleteNotFound { + if err = n.objectDelete(ctx, p.BktInfo, objIDToDelete); err != nil { n.log.Error("couldn't delete notification configuration object", zap.Error(err), zap.String("cnrID", p.BktInfo.CID.EncodeToString()), zap.String("bucket name", p.BktInfo.Name), @@ -67,14 +68,15 @@ func (n *layer) GetBucketNotificationConfiguration(ctx context.Context, bktInfo } objID, err := n.treeService.GetNotificationConfigurationNode(ctx, bktInfo.CID) - if err != nil && !errorsStd.Is(err, ErrNodeNotFound) { + objIDNotFound := errorsStd.Is(err, ErrNodeNotFound) + if err != nil && !objIDNotFound { return nil, err } conf := &data.NotificationConfiguration{} - if objID != nil { - obj, err := n.objectGet(ctx, bktInfo, *objID) + if !objIDNotFound { + obj, err := n.objectGet(ctx, bktInfo, objID) if err != nil { return nil, err } diff --git a/api/layer/object.go b/api/layer/object.go index af99cb65..e0fd9565 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -189,7 +189,7 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Object return nil, err } - newVersion.OID = *id + newVersion.OID = id if err = n.treeService.AddVersion(ctx, p.BktInfo.CID, newVersion); err != nil { return nil, fmt.Errorf("couldn't add new verion to tree service: %w", err) } @@ -209,7 +209,7 @@ func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.Object n.listsCache.CleanCacheEntriesContainingObject(p.Object, p.BktInfo.CID) objInfo := &data.ObjectInfo{ - ID: *id, + ID: id, CID: p.BktInfo.CID, Owner: own, @@ -344,7 +344,7 @@ func (n *layer) objectDelete(ctx context.Context, bktInfo *data.BucketInfo, idOb // objectPutAndHash prepare auth parameters and invoke neofs.CreateObject. // Returns object ID and payload sha256 hash. -func (n *layer) objectPutAndHash(ctx context.Context, prm PrmObjectCreate, bktInfo *data.BucketInfo) (*oid.ID, []byte, error) { +func (n *layer) objectPutAndHash(ctx context.Context, prm PrmObjectCreate, bktInfo *data.BucketInfo) (oid.ID, []byte, error) { n.prepareAuthParameters(ctx, &prm.PrmAuth, bktInfo.Owner) hash := sha256.New() prm.Payload = wrapReader(prm.Payload, 64*1024, func(buf []byte) { diff --git a/api/layer/system_object.go b/api/layer/system_object.go index d12063fa..b8f6d25b 100644 --- a/api/layer/system_object.go +++ b/api/layer/system_object.go @@ -36,18 +36,19 @@ func (n *layer) PutLockInfo(ctx context.Context, objVersion *ObjectVersion, newL } if newLock.Retention != nil { - if lockInfo.RetentionOID != nil { - if lockInfo.IsCompliance { + if lockInfo.IsRetentionSet() { + if lockInfo.IsCompliance() { return fmt.Errorf("you cannot change compliance mode") } if !newLock.Retention.ByPassedGovernance { return fmt.Errorf("you cannot bypass governence mode") } - if len(lockInfo.UntilDate) > 0 { - parsedTime, err := time.Parse(time.RFC3339, lockInfo.UntilDate) + untilDate := lockInfo.UntilDate() + if len(untilDate) > 0 { + parsedTime, err := time.Parse(time.RFC3339, untilDate) if err != nil { - return fmt.Errorf("couldn't parse time '%s': %w", lockInfo.UntilDate, err) + return fmt.Errorf("couldn't parse time '%s': %w", untilDate, err) } if parsedTime.After(newLock.Retention.Until) { return fmt.Errorf("you couldn't short the until date") @@ -55,24 +56,26 @@ func (n *layer) PutLockInfo(ctx context.Context, objVersion *ObjectVersion, newL } } lock := &data.ObjectLock{Retention: newLock.Retention} - if lockInfo.RetentionOID, err = n.putLockObject(ctx, objVersion.BktInfo, versionNode.OID, lock); err != nil { + retentionOID, err := n.putLockObject(ctx, objVersion.BktInfo, versionNode.OID, lock) + if err != nil { return err } - lockInfo.IsCompliance = newLock.Retention.IsCompliance - lockInfo.UntilDate = newLock.Retention.Until.UTC().Format(time.RFC3339) + lockInfo.SetRetention(retentionOID, newLock.Retention.Until.UTC().Format(time.RFC3339), newLock.Retention.IsCompliance) } if newLock.LegalHold != nil { - if newLock.LegalHold.Enabled && lockInfo.LegalHoldOID == nil { + if newLock.LegalHold.Enabled && !lockInfo.IsLegalHoldSet() { lock := &data.ObjectLock{LegalHold: newLock.LegalHold} - if lockInfo.LegalHoldOID, err = n.putLockObject(ctx, objVersion.BktInfo, versionNode.OID, lock); err != nil { + legalHoldOID, err := n.putLockObject(ctx, objVersion.BktInfo, versionNode.OID, lock) + if err != nil { return err } - } else if !newLock.LegalHold.Enabled && lockInfo.LegalHoldOID != nil { - if err = n.objectDelete(ctx, objVersion.BktInfo, *lockInfo.LegalHoldOID); err != nil { - return fmt.Errorf("couldn't delete lock object '%s' to remove legal hold: %w", lockInfo.LegalHoldOID.EncodeToString(), err) + lockInfo.SetLegalHold(legalHoldOID) + } else if !newLock.LegalHold.Enabled && lockInfo.IsLegalHoldSet() { + if err = n.objectDelete(ctx, objVersion.BktInfo, lockInfo.LegalHold()); err != nil { + return fmt.Errorf("couldn't delete lock object '%s' to remove legal hold: %w", lockInfo.LegalHold().EncodeToString(), err) } - lockInfo.LegalHoldOID = nil + lockInfo.ResetLegalHold() } } @@ -87,7 +90,7 @@ func (n *layer) PutLockInfo(ctx context.Context, objVersion *ObjectVersion, newL return nil } -func (n *layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, lock *data.ObjectLock) (*oid.ID, error) { +func (n *layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, objID oid.ID, lock *data.ObjectLock) (oid.ID, error) { prm := PrmObjectCreate{ Container: bktInfo.CID, Creator: bktInfo.Owner, @@ -97,7 +100,7 @@ func (n *layer) putLockObject(ctx context.Context, bktInfo *data.BucketInfo, obj var err error prm.Attributes, err = n.attributesFromLock(ctx, lock) if err != nil { - return nil, err + return oid.ID{}, err } id, _, err := n.objectPutAndHash(ctx, prm, bktInfo) @@ -134,15 +137,16 @@ func (n *layer) getCORS(ctx context.Context, bkt *data.BucketInfo, sysName strin return cors, nil } objID, err := n.treeService.GetBucketCORS(ctx, bkt.CID) - if err != nil { + objIDNotFound := errorsStd.Is(err, ErrNodeNotFound) + if err != nil && !objIDNotFound { return nil, err } - if objID == nil { + if objIDNotFound { return nil, errors.GetAPIError(errors.ErrNoSuchCORSConfiguration) } - obj, err := n.objectGet(ctx, bkt, *objID) + obj, err := n.objectGet(ctx, bkt, objID) if err != nil { return nil, err } diff --git a/api/layer/tree_mock.go b/api/layer/tree_mock.go index a4091b93..cb412ae4 100644 --- a/api/layer/tree_mock.go +++ b/api/layer/tree_mock.go @@ -81,23 +81,23 @@ func (t *TreeServiceMock) GetSettingsNode(_ context.Context, id cid.ID) (*data.B return settings, nil } -func (t *TreeServiceMock) GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (t *TreeServiceMock) GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (oid.ID, error) { panic("implement me") } -func (t *TreeServiceMock) PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) { +func (t *TreeServiceMock) PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) { panic("implement me") } -func (t *TreeServiceMock) GetBucketCORS(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (t *TreeServiceMock) GetBucketCORS(ctx context.Context, cnrID cid.ID) (oid.ID, error) { panic("implement me") } -func (t *TreeServiceMock) PutBucketCORS(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) { +func (t *TreeServiceMock) PutBucketCORS(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) { panic("implement me") } -func (t *TreeServiceMock) DeleteBucketCORS(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (t *TreeServiceMock) DeleteBucketCORS(ctx context.Context, cnrID cid.ID) (oid.ID, error) { panic("implement me") } @@ -294,14 +294,14 @@ func (t *TreeServiceMock) GetMultipartUpload(_ context.Context, cnrID cid.ID, ob return nil, ErrNodeNotFound } -func (t *TreeServiceMock) AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete *oid.ID, err error) { +func (t *TreeServiceMock) AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete oid.ID, err error) { multipartInfo, err := t.GetMultipartUpload(ctx, cnrID, info.Key, info.UploadID) if err != nil { - return nil, err + return oid.ID{}, err } if multipartInfo.ID != multipartNodeID { - return nil, fmt.Errorf("invalid multipart info id") + return oid.ID{}, fmt.Errorf("invalid multipart info id") } partsMap, ok := t.parts[info.UploadID] @@ -312,7 +312,7 @@ func (t *TreeServiceMock) AddPart(ctx context.Context, cnrID cid.ID, multipartNo partsMap[info.Number] = info t.parts[info.UploadID] = partsMap - return nil, nil + return oid.ID{}, nil } func (t *TreeServiceMock) GetParts(_ context.Context, cnrID cid.ID, multipartNodeID uint64) ([]*data.PartInfo, error) { diff --git a/api/layer/tree_service.go b/api/layer/tree_service.go index b1b50570..8e87dce9 100644 --- a/api/layer/tree_service.go +++ b/api/layer/tree_service.go @@ -16,19 +16,34 @@ type TreeService interface { // GetSettingsNode retrieves the settings node from the tree service and form data.BucketSettings. // - // If node is not found returns ErrNodeNotFound error. + // If tree node is not found returns ErrNodeNotFound error. GetSettingsNode(context.Context, cid.ID) (*data.BucketSettings, error) - GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (*oid.ID, error) - // PutNotificationConfigurationNode puts a node to a system tree - // and returns objectID of a previous notif config which must be deleted in NeoFS - PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) + // GetNotificationConfigurationNode gets an object id that corresponds to object with bucket CORS. + // + // If tree node is not found returns ErrNodeNotFound error. + GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (oid.ID, error) - GetBucketCORS(ctx context.Context, cnrID cid.ID) (*oid.ID, error) - // PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in NeoFS - PutBucketCORS(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) - // 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) + // PutNotificationConfigurationNode puts a node to a system tree + // and returns objectID of a previous notif config which must be deleted in NeoFS. + // + // If object id to remove is not found returns ErrNoNodeToRemove error. + PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) + + // GetBucketCORS gets an object id that corresponds to object with bucket CORS. + // + // If object id is not found returns ErrNodeNotFound error. + GetBucketCORS(ctx context.Context, cnrID cid.ID) (oid.ID, error) + + // PutBucketCORS puts a node to a system tree and returns objectID of a previous cors config which must be deleted in NeoFS. + // + // If object id to remove is not found returns ErrNoNodeToRemove error. + PutBucketCORS(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) + + // DeleteBucketCORS removes a node from a system tree and returns objID which must be deleted in NeoFS. + // + // If object id to remove is not found returns ErrNoNodeToRemove error. + 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 @@ -56,8 +71,9 @@ type TreeService interface { // AddPart puts a node to a system tree as a child of appropriate multipart upload // and returns objectID of a previous part which must be deleted in NeoFS. - // If a part is being added for the first time, the previous part ID will be nil. - AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete *oid.ID, err error) + // + // If object id to remove is not found returns ErrNoNodeToRemove error. + AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete oid.ID, err error) GetParts(ctx context.Context, cnrID cid.ID, multipartNodeID uint64) ([]*data.PartInfo, error) // Compound methods for optimizations @@ -66,5 +82,10 @@ type TreeService interface { GetObjectTaggingAndLock(ctx context.Context, cnrID cid.ID, objVersion *data.NodeVersion) (map[string]string, *data.LockInfo, error) } -// ErrNodeNotFound is returned from Tree service in case of not found error. -var ErrNodeNotFound = errors.New("not found") +var ( + // ErrNodeNotFound is returned from Tree service in case of not found error. + ErrNodeNotFound = errors.New("not found") + + // ErrNoNodeToRemove is returned from Tree service in case of the lack of node with OID to remove. + ErrNoNodeToRemove = errors.New("no node to remove") +) diff --git a/authmate/session_tokens_test.go b/authmate/session_tokens_test.go index 5ac3065a..add97ea1 100644 --- a/authmate/session_tokens_test.go +++ b/authmate/session_tokens_test.go @@ -3,7 +3,6 @@ package authmate import ( "testing" - cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/stretchr/testify/require" ) @@ -29,9 +28,9 @@ func TestContainerSessionRules(t *testing.T) { require.Len(t, sessionContext, 3) require.Equal(t, sessionContext[0].verb, session.VerbContainerPut) - require.Equal(t, cid.ID{}, sessionContext[0].containerID) + require.Zero(t, sessionContext[0].containerID) require.Equal(t, sessionContext[1].verb, session.VerbContainerDelete) require.NotNil(t, sessionContext[1].containerID) require.Equal(t, sessionContext[2].verb, session.VerbContainerSetEACL) - require.Equal(t, cid.ID{}, sessionContext[2].containerID) + require.Zero(t, sessionContext[2].containerID) } diff --git a/creds/tokens/credentials.go b/creds/tokens/credentials.go index 85beb68d..e6f6666d 100644 --- a/creds/tokens/credentials.go +++ b/creds/tokens/credentials.go @@ -55,7 +55,7 @@ type NeoFS interface { // // It returns exactly one non-nil value. It returns any error encountered which // prevented the object from being created. - CreateObject(context.Context, PrmObjectCreate) (*oid.ID, error) + CreateObject(context.Context, PrmObjectCreate) (oid.ID, error) // ReadObjectPayload reads payload of the object from NeoFS network by address // into memory. @@ -140,7 +140,7 @@ func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *acces } var addr oid.Address - addr.SetObject(*idObj) + addr.SetObject(idObj) addr.SetContainer(idCnr) return &addr, nil diff --git a/internal/neofs/neofs.go b/internal/neofs/neofs.go index 9b88fbc2..714b9423 100644 --- a/internal/neofs/neofs.go +++ b/internal/neofs/neofs.go @@ -215,7 +215,7 @@ func (x *NeoFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.C } // CreateObject implements neofs.NeoFS interface method. -func (x *NeoFS) CreateObject(ctx context.Context, prm layer.PrmObjectCreate) (*oid.ID, error) { +func (x *NeoFS) CreateObject(ctx context.Context, prm layer.PrmObjectCreate) (oid.ID, error) { attrNum := len(prm.Attributes) + 1 // + creation time if prm.Filename != "" { @@ -270,12 +270,12 @@ func (x *NeoFS) CreateObject(ctx context.Context, prm layer.PrmObjectCreate) (*o if err != nil { reason, ok := isErrAccessDenied(err) if ok { - return nil, fmt.Errorf("%w: %s", layer.ErrAccessDenied, reason) + return oid.ID{}, fmt.Errorf("%w: %s", layer.ErrAccessDenied, reason) } - return nil, fmt.Errorf("save object via connection pool: %w", err) + return oid.ID{}, fmt.Errorf("save object via connection pool: %w", err) } - return idObj, nil + return *idObj, nil } // SelectObjects implements neofs.NeoFS interface method. @@ -570,7 +570,7 @@ func (x *AuthmateNeoFS) ReadObjectPayload(ctx context.Context, addr oid.Address) } // CreateObject implements authmate.NeoFS interface method. -func (x *AuthmateNeoFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (*oid.ID, error) { +func (x *AuthmateNeoFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (oid.ID, error) { return x.neoFS.CreateObject(ctx, layer.PrmObjectCreate{ Creator: prm.Creator, Container: prm.Container, diff --git a/internal/neofs/tree.go b/internal/neofs/tree.go index c3e4165d..2b79c6e9 100644 --- a/internal/neofs/tree.go +++ b/internal/neofs/tree.go @@ -300,20 +300,20 @@ func (c *TreeClient) PutSettingsNode(ctx context.Context, cnrID cid.ID, settings return c.moveNode(ctx, cnrID, systemTree, node.ID, 0, meta) } -func (c *TreeClient) GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (c *TreeClient) GetNotificationConfigurationNode(ctx context.Context, cnrID cid.ID) (oid.ID, error) { node, err := c.getSystemNode(ctx, cnrID, []string{notifConfFileName}, []string{oidKV}) if err != nil { - return nil, err + return oid.ID{}, err } - return &node.ObjID, nil + return node.ObjID, nil } -func (c *TreeClient) PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) { +func (c *TreeClient) PutNotificationConfigurationNode(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) { node, err := c.getSystemNode(ctx, cnrID, []string{notifConfFileName}, []string{oidKV}) isErrNotFound := errors.Is(err, layer.ErrNodeNotFound) if err != nil && !isErrNotFound { - return nil, fmt.Errorf("couldn't get node: %w", err) + return oid.ID{}, fmt.Errorf("couldn't get node: %w", err) } meta := make(map[string]string) @@ -321,27 +321,29 @@ func (c *TreeClient) PutNotificationConfigurationNode(ctx context.Context, cnrID meta[oidKV] = objID.EncodeToString() if isErrNotFound { - _, err = c.addNode(ctx, cnrID, systemTree, 0, meta) - return nil, err + if _, err = c.addNode(ctx, cnrID, systemTree, 0, meta); err != nil { + return oid.ID{}, err + } + return oid.ID{}, layer.ErrNoNodeToRemove } - return &node.ObjID, c.moveNode(ctx, cnrID, systemTree, node.ID, 0, meta) + return node.ObjID, c.moveNode(ctx, cnrID, systemTree, node.ID, 0, meta) } -func (c *TreeClient) GetBucketCORS(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (c *TreeClient) GetBucketCORS(ctx context.Context, cnrID cid.ID) (oid.ID, error) { node, err := c.getSystemNode(ctx, cnrID, []string{corsFilename}, []string{oidKV}) if err != nil { - return nil, err + return oid.ID{}, err } - return &node.ObjID, nil + return node.ObjID, nil } -func (c *TreeClient) PutBucketCORS(ctx context.Context, cnrID cid.ID, objID *oid.ID) (*oid.ID, error) { +func (c *TreeClient) PutBucketCORS(ctx context.Context, cnrID cid.ID, objID oid.ID) (oid.ID, error) { node, err := c.getSystemNode(ctx, cnrID, []string{corsFilename}, []string{oidKV}) isErrNotFound := errors.Is(err, layer.ErrNodeNotFound) if err != nil && !isErrNotFound { - return nil, fmt.Errorf("couldn't get node: %w", err) + return oid.ID{}, fmt.Errorf("couldn't get node: %w", err) } meta := make(map[string]string) @@ -349,24 +351,26 @@ func (c *TreeClient) PutBucketCORS(ctx context.Context, cnrID cid.ID, objID *oid meta[oidKV] = objID.EncodeToString() if isErrNotFound { - _, err = c.addNode(ctx, cnrID, systemTree, 0, meta) - return nil, err + if _, err = c.addNode(ctx, cnrID, systemTree, 0, meta); err != nil { + return oid.ID{}, err + } + return oid.ID{}, layer.ErrNoNodeToRemove } - return &node.ObjID, c.moveNode(ctx, cnrID, systemTree, node.ID, 0, meta) + return node.ObjID, c.moveNode(ctx, cnrID, systemTree, node.ID, 0, meta) } -func (c *TreeClient) DeleteBucketCORS(ctx context.Context, cnrID cid.ID) (*oid.ID, error) { +func (c *TreeClient) DeleteBucketCORS(ctx context.Context, cnrID cid.ID) (oid.ID, error) { node, err := c.getSystemNode(ctx, cnrID, []string{corsFilename}, []string{oidKV}) if err != nil && !errors.Is(err, layer.ErrNodeNotFound) { - return nil, err + return oid.ID{}, err } if node != nil { - return &node.ObjID, c.removeNode(ctx, cnrID, systemTree, node.ID) + return node.ObjID, c.removeNode(ctx, cnrID, systemTree, node.ID) } - return nil, nil + return oid.ID{}, layer.ErrNoNodeToRemove } func (c *TreeClient) GetObjectTagging(ctx context.Context, cnrID cid.ID, objVersion *data.NodeVersion) (map[string]string, error) { @@ -881,10 +885,10 @@ func (c *TreeClient) GetMultipartUpload(ctx context.Context, cnrID cid.ID, objec return nil, layer.ErrNodeNotFound } -func (c *TreeClient) AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete *oid.ID, err error) { +func (c *TreeClient) AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID uint64, info *data.PartInfo) (oldObjIDToDelete oid.ID, err error) { parts, err := c.getSubTree(ctx, cnrID, systemTree, multipartNodeID, 1) if err != nil { - return nil, err + return oid.ID{}, err } meta := map[string]string{ @@ -906,14 +910,16 @@ func (c *TreeClient) AddPart(ctx context.Context, cnrID cid.ID, multipartNodeID } if partInfo.Number == info.Number { foundPartID = part.GetNodeId() - oldObjIDToDelete = &partInfo.OID + oldObjIDToDelete = partInfo.OID break } } - if oldObjIDToDelete == nil { - _, err = c.addNode(ctx, cnrID, systemTree, multipartNodeID, meta) - return nil, err + if foundPartID != multipartNodeID { + if _, err = c.addNode(ctx, cnrID, systemTree, multipartNodeID, meta); err != nil { + return oid.ID{}, err + } + return oid.ID{}, layer.ErrNoNodeToRemove } return oldObjIDToDelete, c.moveNode(ctx, cnrID, systemTree, foundPartID, multipartNodeID, meta) @@ -947,23 +953,23 @@ func (c *TreeClient) DeleteMultipartUpload(ctx context.Context, cnrID cid.ID, mu func (c *TreeClient) PutLock(ctx context.Context, cnrID cid.ID, nodeID uint64, lock *data.LockInfo) error { meta := map[string]string{isLockKV: "true"} - if lock.LegalHoldOID != nil { - meta[legalHoldOIDKV] = lock.LegalHoldOID.EncodeToString() + if lock.IsLegalHoldSet() { + meta[legalHoldOIDKV] = lock.LegalHold().EncodeToString() } - if lock.RetentionOID != nil { - meta[retentionOIDKV] = lock.RetentionOID.EncodeToString() - meta[untilDateKV] = lock.UntilDate - if lock.IsCompliance { + if lock.IsRetentionSet() { + meta[retentionOIDKV] = lock.Retention().EncodeToString() + meta[untilDateKV] = lock.UntilDate() + if lock.IsCompliance() { meta[isComplianceKV] = "true" } } - if lock.ID == 0 { + if lock.ID() == 0 { _, err := c.addNode(ctx, cnrID, versionTree, nodeID, meta) return err } - return c.moveNode(ctx, cnrID, versionTree, lock.ID, nodeID, meta) + return c.moveNode(ctx, cnrID, versionTree, lock.ID(), nodeID, meta) } func (c *TreeClient) GetLock(ctx context.Context, cnrID cid.ID, nodeID uint64) (*data.LockInfo, error) { @@ -976,18 +982,17 @@ func (c *TreeClient) GetLock(ctx context.Context, cnrID cid.ID, nodeID uint64) ( } func getLock(lockNode *TreeNode) (*data.LockInfo, error) { - lockInfo := &data.LockInfo{} if lockNode == nil { - return lockInfo, nil + return &data.LockInfo{}, nil } - lockInfo.ID = lockNode.ID + lockInfo := data.NewLockInfo(lockNode.ID) if legalHold, ok := lockNode.Get(legalHoldOIDKV); ok { var legalHoldOID oid.ID if err := legalHoldOID.DecodeString(legalHold); err != nil { return nil, fmt.Errorf("invalid legal hold object id: %w", err) } - lockInfo.LegalHoldOID = &legalHoldOID + lockInfo.SetLegalHold(legalHoldOID) } if retention, ok := lockNode.Get(retentionOIDKV); ok { @@ -995,12 +1000,11 @@ func getLock(lockNode *TreeNode) (*data.LockInfo, error) { if err := retentionOID.DecodeString(retention); err != nil { return nil, fmt.Errorf("invalid retention object id: %w", err) } - lockInfo.RetentionOID = &retentionOID + _, isCompliance := lockNode.Get(isComplianceKV) + untilDate, _ := lockNode.Get(untilDateKV) + lockInfo.SetRetention(retentionOID, untilDate, isCompliance) } - _, lockInfo.IsCompliance = lockNode.Get(isComplianceKV) - lockInfo.UntilDate, _ = lockNode.Get(untilDateKV) - return lockInfo, nil }