[#122] Add delete versioned object

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
Denis Kirillov 2021-08-10 15:08:15 +03:00
parent 3130784ee6
commit d5aef7566f
7 changed files with 189 additions and 86 deletions

View file

@ -66,6 +66,7 @@ const (
ErrNoSuchKey ErrNoSuchKey
ErrNoSuchUpload ErrNoSuchUpload
ErrNoSuchVersion ErrNoSuchVersion
ErrInvalidVersion
ErrNotImplemented ErrNotImplemented
ErrPreconditionFailed ErrPreconditionFailed
ErrNotModified ErrNotModified
@ -529,6 +530,12 @@ var errorCodes = errorCodeMap{
Description: "Indicates that the version ID specified in the request does not match an existing version.", Description: "Indicates that the version ID specified in the request does not match an existing version.",
HTTPStatusCode: http.StatusNotFound, HTTPStatusCode: http.StatusNotFound,
}, },
ErrInvalidVersion: {
ErrCode: ErrInvalidVersion,
Code: "InvalidArgument",
Description: "Invalid version id specified",
HTTPStatusCode: http.StatusBadRequest,
},
ErrNotImplemented: { ErrNotImplemented: {
ErrCode: ErrNotImplemented, ErrCode: ErrNotImplemented,
Code: "NotImplemented", Code: "NotImplemented",

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neofs-s3-gw/api/errors" "github.com/nspcc-dev/neofs-s3-gw/api/errors"
"github.com/nspcc-dev/neofs-s3-gw/api/layer" "github.com/nspcc-dev/neofs-s3-gw/api/layer"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
// DeleteObjectsRequest - xml carrying the object key names which needs to be deleted. // DeleteObjectsRequest - xml carrying the object key names which needs to be deleted.
@ -21,6 +22,7 @@ type DeleteObjectsRequest struct {
// ObjectIdentifier carries key name for the object to delete. // ObjectIdentifier carries key name for the object to delete.
type ObjectIdentifier struct { type ObjectIdentifier struct {
ObjectName string `xml:"Key"` ObjectName string `xml:"Key"`
VersionID string `xml:"VersionId,omitempty"`
} }
// DeleteError structure. // DeleteError structure.
@ -43,18 +45,22 @@ type DeleteObjectsResponse struct {
func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
reqInfo := api.GetReqInfo(r.Context()) reqInfo := api.GetReqInfo(r.Context())
versionedObject := []*layer.VersionedObject{{
Name: reqInfo.ObjectName,
VersionID: reqInfo.URL.Query().Get("versionId"),
}}
if err := h.checkBucketOwner(r, reqInfo.BucketName); err != nil { if err := h.checkBucketOwner(r, reqInfo.BucketName); err != nil {
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err) h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
return return
} }
if err := h.obj.DeleteObject(r.Context(), reqInfo.BucketName, reqInfo.ObjectName); err != nil { if errs := h.obj.DeleteObjects(r.Context(), reqInfo.BucketName, versionedObject); len(errs) != 0 && errs[0] != nil {
h.log.Error("could not delete object", h.log.Error("could not delete object",
zap.String("request_id", reqInfo.RequestID), zap.String("request_id", reqInfo.RequestID),
zap.String("bucket_name", reqInfo.BucketName), zap.String("bucket_name", reqInfo.BucketName),
zap.String("object_name", reqInfo.ObjectName), zap.String("object_name", reqInfo.ObjectName),
zap.Error(err)) zap.Error(errs[0]))
// Ignore delete errors: // Ignore delete errors:
@ -94,10 +100,14 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
} }
removed := make(map[string]struct{}) removed := make(map[string]struct{})
toRemove := make([]string, 0, len(requested.Objects)) toRemove := make([]*layer.VersionedObject, 0, len(requested.Objects))
for _, obj := range requested.Objects { for _, obj := range requested.Objects {
removed[obj.ObjectName] = struct{}{} versionedObj := &layer.VersionedObject{
toRemove = append(toRemove, obj.ObjectName) Name: obj.ObjectName,
VersionID: obj.VersionID,
}
toRemove = append(toRemove, versionedObj)
removed[versionedObj.String()] = struct{}{}
} }
response := &DeleteObjectsResponse{ response := &DeleteObjectsResponse{
@ -110,9 +120,16 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
return return
} }
marshaler := zapcore.ArrayMarshalerFunc(func(encoder zapcore.ArrayEncoder) error {
for _, obj := range toRemove {
encoder.AppendString(obj.String())
}
return nil
})
if errs := h.obj.DeleteObjects(r.Context(), reqInfo.BucketName, toRemove); errs != nil && !requested.Quiet { if errs := h.obj.DeleteObjects(r.Context(), reqInfo.BucketName, toRemove); errs != nil && !requested.Quiet {
additional := []zap.Field{ additional := []zap.Field{
zap.Strings("objects_name", toRemove), zap.Array("objects", marshaler),
zap.Errors("errors", errs), zap.Errors("errors", errs),
} }
h.logAndSendError(w, "could not delete objects", reqInfo, nil, additional...) h.logAndSendError(w, "could not delete objects", reqInfo, nil, additional...)
@ -138,7 +155,7 @@ func (h *handler) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Re
} }
if err := api.EncodeToResponse(w, response); err != nil { if err := api.EncodeToResponse(w, response); err != nil {
h.logAndSendError(w, "could not write response", reqInfo, err, zap.Strings("objects_name", toRemove)) h.logAndSendError(w, "could not write response", reqInfo, err, zap.Array("objects", marshaler))
return return
} }
} }

View file

@ -72,7 +72,7 @@ func writeHeaders(h http.Header, info *layer.ObjectInfo) {
h.Set(api.LastModified, info.Created.UTC().Format(http.TimeFormat)) h.Set(api.LastModified, info.Created.UTC().Format(http.TimeFormat))
h.Set(api.ContentLength, strconv.FormatInt(info.Size, 10)) h.Set(api.ContentLength, strconv.FormatInt(info.Size, 10))
h.Set(api.ETag, info.HashSum) h.Set(api.ETag, info.HashSum)
h.Set(api.AmzVersionId, info.ID().String()) h.Set(api.AmzVersionID, info.ID().String())
for key, val := range info.Headers { for key, val := range info.Headers {
h[api.MetadataPrefix+key] = []string{val} h[api.MetadataPrefix+key] = []string{val}

View file

@ -4,7 +4,7 @@ package api
const ( const (
MetadataPrefix = "X-Amz-Meta-" MetadataPrefix = "X-Amz-Meta-"
AmzMetadataDirective = "X-Amz-Metadata-Directive" AmzMetadataDirective = "X-Amz-Metadata-Directive"
AmzVersionId = "X-Amz-Version-Id" AmzVersionID = "X-Amz-Version-Id"
LastModified = "Last-Modified" LastModified = "Last-Modified"
Date = "Date" Date = "Date"

View file

@ -1,6 +1,7 @@
package layer package layer
import ( import (
"bytes"
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"fmt" "fmt"
@ -8,6 +9,7 @@ import (
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
@ -128,6 +130,12 @@ type (
Encode string Encode string
} }
// VersionedObject stores object name and version.
VersionedObject struct {
Name string
VersionID string
}
// NeoFS provides basic NeoFS interface. // NeoFS provides basic NeoFS interface.
NeoFS interface { NeoFS interface {
Get(ctx context.Context, address *object.Address) (*object.Object, error) Get(ctx context.Context, address *object.Address) (*object.Object, error)
@ -158,8 +166,7 @@ type (
ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*ListObjectsInfoV2, error) ListObjectsV2(ctx context.Context, p *ListObjectsParamsV2) (*ListObjectsInfoV2, error)
ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error) ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error)
DeleteObject(ctx context.Context, bucket, object string) error DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) []error
DeleteObjects(ctx context.Context, bucket string, objects []string) []error
} }
) )
@ -168,6 +175,10 @@ const (
bktVersionSettingsObject = ".s3-versioning-settings" bktVersionSettingsObject = ".s3-versioning-settings"
) )
func (t *VersionedObject) String() string {
return t.Name + ":" + t.VersionID
}
// NewLayer creates instance of layer. It checks credentials // NewLayer creates instance of layer. It checks credentials
// and establishes gRPC connection with node. // and establishes gRPC connection with node.
func NewLayer(log *zap.Logger, conns pool.Pool, config *CacheConfig) Client { func NewLayer(log *zap.Logger, conns pool.Pool, config *CacheConfig) Client {
@ -330,16 +341,21 @@ func (n *layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*Object
} }
if len(p.VersionID) == 0 { if len(p.VersionID) == 0 {
return n.headLastVersion(ctx, bkt, p.Object) objInfo, err := n.headLastVersion(ctx, bkt, p.Object)
if err == nil {
if deleteMark, err2 := strconv.ParseBool(objInfo.Headers[versionsDeleteMarkAttr]); err2 == nil && deleteMark {
return nil, api.GetAPIError(api.ErrNoSuchKey)
}
}
return objInfo, err
} }
return n.headVersion(ctx, bkt, p.Object, p.VersionID) return n.headVersion(ctx, bkt, p.VersionID)
} }
func (n *layer) getSettingsObjectInfo(ctx context.Context, bkt *BucketInfo) (*ObjectInfo, error) { func (n *layer) getSettingsObjectInfo(ctx context.Context, bkt *BucketInfo) (*ObjectInfo, error) {
oid, err := n.objectFindID(ctx, &findParams{cid: bkt.CID, val: bktVersionSettingsObject}) oid, err := n.objectFindID(ctx, &findParams{cid: bkt.CID, val: bktVersionSettingsObject})
if err != nil { if err != nil {
n.log.Error("could not find object id", zap.Error(err))
return nil, err return nil, err
} }
@ -367,7 +383,12 @@ func (n *layer) getSettingsObjectInfo(ctx context.Context, bkt *BucketInfo) (*Ob
// PutObject into storage. // PutObject into storage.
func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*ObjectInfo, error) { func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*ObjectInfo, error) {
return n.objectPut(ctx, p) bkt, err := n.GetBucketInfo(ctx, p.Bucket)
if err != nil {
return nil, err
}
return n.objectPut(ctx, bkt, p)
} }
// CopyObject from one bucket into another bucket. // CopyObject from one bucket into another bucket.
@ -395,35 +416,96 @@ func (n *layer) CopyObject(ctx context.Context, p *CopyObjectParams) (*ObjectInf
} }
// DeleteObject removes all objects with passed nice name. // DeleteObject removes all objects with passed nice name.
func (n *layer) DeleteObject(ctx context.Context, bucket, filename string) error { func (n *layer) deleteObject(ctx context.Context, bkt *BucketInfo, obj *VersionedObject) error {
var ( var (
err error err error
ids []*object.ID ids []*object.ID
bkt *BucketInfo
) )
if bkt, err = n.GetBucketInfo(ctx, bucket); err != nil { versioningEnabled := n.isVersioningEnabled(ctx, bkt)
return &errors.DeleteError{ if !versioningEnabled && obj.VersionID != "null" && obj.VersionID != "" {
Err: err, return errors.GetAPIError(errors.ErrInvalidVersion)
Object: filename, }
if versioningEnabled {
if len(obj.VersionID) != 0 {
id := object.NewID()
if err := id.Parse(obj.VersionID); err != nil {
return &errors.DeleteError{Err: api.GetAPIError(api.ErrInvalidVersion), Object: obj.String()}
}
ids = []*object.ID{id}
lastObject, err := n.headLastVersion(ctx, bkt, obj.Name)
if err != nil {
return &api.DeleteError{Err: err, Object: obj.String()}
}
if !strings.Contains(lastObject.Headers[versionsAddAttr], obj.VersionID) ||
strings.Contains(lastObject.Headers[versionsDelAttr], obj.VersionID) {
return &api.DeleteError{Err: api.GetAPIError(api.ErrInvalidVersion), Object: obj.String()}
}
if lastObject.ID().String() == obj.VersionID {
if added := lastObject.Headers[versionsAddAttr]; len(added) > 0 {
addedVersions := strings.Split(added, ",")
sourceCopyVersion, err := n.headVersion(ctx, bkt, addedVersions[len(addedVersions)-1])
if err != nil {
return &api.DeleteError{Err: err, Object: obj.String()}
}
p := &CopyObjectParams{
SrcObject: sourceCopyVersion,
DstBucket: bkt.Name,
DstObject: obj.Name,
SrcSize: sourceCopyVersion.Size,
Header: map[string]string{versionsDelAttr: obj.VersionID},
}
if _, err := n.CopyObject(ctx, p); err != nil {
return err
}
} else {
p := &PutObjectParams{
Object: obj.Name,
Reader: bytes.NewReader(nil),
Header: map[string]string{
versionsDelAttr: obj.VersionID,
versionsDeleteMarkAttr: strconv.FormatBool(true),
},
}
if _, err := n.objectPut(ctx, bkt, p); err != nil {
return &api.DeleteError{Err: err, Object: obj.String()}
}
}
} else {
p := &CopyObjectParams{
SrcObject: lastObject,
DstBucket: bkt.Name,
DstObject: obj.Name,
SrcSize: lastObject.Size,
Header: map[string]string{versionsDelAttr: obj.VersionID},
}
if _, err := n.CopyObject(ctx, p); err != nil {
return err
}
}
} else {
p := &PutObjectParams{
Object: obj.Name,
Reader: bytes.NewReader(nil),
Header: map[string]string{versionsDeleteMarkAttr: strconv.FormatBool(true)},
}
if _, err := n.objectPut(ctx, bkt, p); err != nil {
return &errors.DeleteError{Err: err, Object: obj.String()}
}
} }
} else if ids, err = n.objectSearch(ctx, &findParams{cid: bkt.CID, val: filename}); err != nil { } else {
return &errors.DeleteError{ ids, err = n.objectSearch(ctx, &findParams{cid: bkt.CID, val: obj.Name})
Err: err, if err != nil {
Object: filename, return &errors.DeleteError{Err: err, Object: obj.String()}
} }
} }
for _, id := range ids { for _, id := range ids {
addr := object.NewAddress() if err = n.objectDelete(ctx, bkt.CID, id); err != nil {
addr.SetObjectID(id) return &errors.DeleteError{Err: err, Object: obj.String()}
addr.SetContainerID(bkt.CID)
if err = n.objectDelete(ctx, addr); err != nil {
return &errors.DeleteError{
Err: err,
Object: filename,
}
} }
} }
@ -431,11 +513,16 @@ func (n *layer) DeleteObject(ctx context.Context, bucket, filename string) error
} }
// DeleteObjects from the storage. // DeleteObjects from the storage.
func (n *layer) DeleteObjects(ctx context.Context, bucket string, objects []string) []error { func (n *layer) DeleteObjects(ctx context.Context, bucket string, objects []*VersionedObject) []error {
var errs = make([]error, 0, len(objects)) var errs = make([]error, 0, len(objects))
bkt, err := n.GetBucketInfo(ctx, bucket)
if err != nil {
return append(errs, err)
}
for i := range objects { for i := range objects {
if err := n.DeleteObject(ctx, bucket, objects[i]); err != nil { if err := n.deleteObject(ctx, bkt, objects[i]); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
} }
@ -461,6 +548,14 @@ func (n *layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
return err return err
} }
ids, err := n.objectSearch(ctx, &findParams{cid: bucketInfo.CID})
if err != nil {
return err
}
if len(ids) != 0 {
return api.GetAPIError(api.ErrBucketNotEmpty)
}
return n.deleteContainer(ctx, bucketInfo.CID) return n.deleteContainer(ctx, bucketInfo.CID)
} }
@ -581,10 +676,7 @@ func (n *layer) PutBucketVersioning(ctx context.Context, p *PutVersioningParams)
} }
if objectInfo != nil { if objectInfo != nil {
addr := object.NewAddress() if err = n.objectDelete(ctx, bucketInfo.CID, objectInfo.ID()); err != nil {
addr.SetObjectID(objectInfo.ID())
addr.SetContainerID(bucketInfo.CID)
if err = n.objectDelete(ctx, addr); err != nil {
return nil, err return nil, err
} }
} }

View file

@ -64,8 +64,9 @@ type (
) )
const ( const (
versionsDelAttr = "S3-Versions-del" versionsDelAttr = "S3-Versions-del"
versionsAddAttr = "S3-Versions-add" versionsAddAttr = "S3-Versions-add"
versionsDeleteMarkAttr = "S3-Versions-delete-mark"
) )
// objectSearch returns all available objects by search params. // objectSearch returns all available objects by search params.
@ -121,11 +122,10 @@ func (n *layer) objectRange(ctx context.Context, p *getParams) ([]byte, error) {
} }
// objectPut into NeoFS, took payload from io.Reader. // objectPut into NeoFS, took payload from io.Reader.
func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo, error) { func (n *layer) objectPut(ctx context.Context, bkt *BucketInfo, p *PutObjectParams) (*ObjectInfo, error) {
var ( var (
err error err error
obj string obj string
bkt *BucketInfo
own = n.Owner(ctx) own = n.Owner(ctx)
) )
@ -136,9 +136,6 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
if obj, err = url.QueryUnescape(p.Object); err != nil { if obj, err = url.QueryUnescape(p.Object); err != nil {
return nil, err return nil, err
} }
if bkt, err = n.GetBucketInfo(ctx, p.Bucket); err != nil {
return nil, err
}
versioningEnabled := n.isVersioningEnabled(ctx, bkt) versioningEnabled := n.isVersioningEnabled(ctx, bkt)
lastVersionInfo, err := n.headLastVersion(ctx, bkt, p.Object) lastVersionInfo, err := n.headLastVersion(ctx, bkt, p.Object)
@ -155,15 +152,18 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
versionsAddedStr += "," versionsAddedStr += ","
} }
versionsAddedStr += lastVersionInfo.ID().String() versionsAddedStr += lastVersionInfo.ID().String()
addedVersions := object.NewAttribute() p.Header[versionsAddAttr] = versionsAddedStr
addedVersions.SetKey(versionsAddAttr)
addedVersions.SetValue(versionsAddedStr) deleted := p.Header[versionsDelAttr]
attributes = append(attributes, addedVersions) if delVersions := lastVersionInfo.Headers[versionsDelAttr]; len(delVersions) != 0 {
if delVersions := lastVersionInfo.Headers[versionsDelAttr]; len(delVersions) > 0 { if len(deleted) == 0 {
deletedVersions := object.NewAttribute() deleted = delVersions
deletedVersions.SetKey(versionsDelAttr) } else {
deletedVersions.SetValue(delVersions) deleted = delVersions + "," + deleted
attributes = append(attributes, deletedVersions) }
}
if len(deleted) != 0 {
p.Header[versionsDelAttr] = deleted
} }
} else { } else {
versionsDeletedStr := lastVersionInfo.Headers[versionsDelAttr] versionsDeletedStr := lastVersionInfo.Headers[versionsDelAttr]
@ -171,24 +171,19 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
versionsDeletedStr += "," versionsDeletedStr += ","
} }
versionsDeletedStr += lastVersionInfo.ID().String() versionsDeletedStr += lastVersionInfo.ID().String()
deletedVersions := object.NewAttribute() p.Header[versionsDelAttr] = versionsDeletedStr
deletedVersions.SetKey(versionsDelAttr)
deletedVersions.SetValue(versionsDeletedStr)
attributes = append(attributes, deletedVersions)
idsToDeleteArr = append(idsToDeleteArr, lastVersionInfo.ID()) idsToDeleteArr = append(idsToDeleteArr, lastVersionInfo.ID())
} }
} }
unix := strconv.FormatInt(time.Now().UTC().Unix(), 10)
filename := object.NewAttribute() filename := object.NewAttribute()
filename.SetKey(object.AttributeFileName) filename.SetKey(object.AttributeFileName)
filename.SetValue(obj) filename.SetValue(obj)
createdAt := object.NewAttribute() createdAt := object.NewAttribute()
createdAt.SetKey(object.AttributeTimestamp) createdAt.SetKey(object.AttributeTimestamp)
createdAt.SetValue(unix) createdAt.SetValue(strconv.FormatInt(time.Now().UTC().Unix(), 10))
attributes = append(attributes, filename, createdAt) attributes = append(attributes, filename, createdAt)
@ -240,11 +235,7 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo,
} }
for _, id := range idsToDeleteArr { for _, id := range idsToDeleteArr {
addr := object.NewAddress() if err = n.objectDelete(ctx, bkt.CID, id); err != nil {
addr.SetObjectID(id)
addr.SetContainerID(bkt.CID)
if err = n.objectDelete(ctx, addr); err != nil {
n.log.Warn("couldn't delete object", n.log.Warn("couldn't delete object",
zap.Stringer("version id", id), zap.Stringer("version id", id),
zap.Error(err)) zap.Error(err))
@ -284,33 +275,28 @@ func (n *layer) headLastVersion(ctx context.Context, bkt *BucketInfo, objectName
return objectInfoFromMeta(bkt, infos[len(infos)-1], "", ""), nil return objectInfoFromMeta(bkt, infos[len(infos)-1], "", ""), nil
} }
func (n *layer) headVersion(ctx context.Context, bkt *BucketInfo, objectName, versionID string) (*ObjectInfo, error) { func (n *layer) headVersion(ctx context.Context, bkt *BucketInfo, versionID string) (*ObjectInfo, error) {
ids, err := n.objectSearch(ctx, &findParams{cid: bkt.CID, val: objectName}) oid := object.NewID()
if err != nil { if err := oid.Parse(versionID); err != nil {
return nil, err return nil, err
} }
if len(ids) == 0 {
return nil, api.GetAPIError(api.ErrNoSuchVersion)
}
for _, id := range ids { meta, err := n.objectHead(ctx, bkt.CID, oid)
if id.String() == versionID { if err != nil {
meta, err := n.objectHead(ctx, bkt.CID, id) if strings.Contains(err.Error(), "not found") {
if err != nil { return nil, api.GetAPIError(api.ErrNoSuchVersion)
if strings.Contains(err.Error(), "not found") {
return nil, api.GetAPIError(api.ErrNoSuchVersion)
}
return nil, err
}
return objectInfoFromMeta(bkt, meta, "", ""), nil
} }
return nil, err
} }
return nil, api.GetAPIError(api.ErrNoSuchVersion) return objectInfoFromMeta(bkt, meta, "", ""), nil
} }
// objectDelete puts tombstone object into neofs. // objectDelete puts tombstone object into neofs.
func (n *layer) objectDelete(ctx context.Context, address *object.Address) error { func (n *layer) objectDelete(ctx context.Context, cid *cid.ID, oid *object.ID) error {
address := object.NewAddress()
address.SetContainerID(cid)
address.SetObjectID(oid)
dop := new(client.DeleteObjectParams) dop := new(client.DeleteObjectParams)
dop.WithAddress(address) dop.WithAddress(address)
n.objCache.Delete(address) n.objCache.Delete(address)

View file

@ -48,6 +48,7 @@ func newTestInfo(oid *object.ID, bkt *BucketInfo, name string, isDir bool) *Obje
id: oid, id: oid,
Name: name, Name: name,
Bucket: bkt.Name, Bucket: bkt.Name,
bucketID: bkt.CID,
Size: defaultTestPayloadLength, Size: defaultTestPayloadLength,
ContentType: defaultTestContentType, ContentType: defaultTestContentType,
Created: time.Unix(defaultTestCreated.Unix(), 0), Created: time.Unix(defaultTestCreated.Unix(), 0),