forked from TrueCloudLab/frostfs-s3-gw
[#122] Add list object versions
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
d5aef7566f
commit
43185de52a
5 changed files with 127 additions and 56 deletions
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
@ -14,7 +15,7 @@ func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Requ
|
||||||
|
|
||||||
configuration := new(VersioningConfiguration)
|
configuration := new(VersioningConfiguration)
|
||||||
if err := xml.NewDecoder(r.Body).Decode(configuration); err != nil {
|
if err := xml.NewDecoder(r.Body).Decode(configuration); err != nil {
|
||||||
h.logAndSendError(w, "couldn't decode versioning configuration", reqInfo, api.GetAPIError(api.ErrIllegalVersioningConfigurationException))
|
h.logAndSendError(w, "couldn't decode versioning configuration", reqInfo, errors.GetAPIError(errors.ErrIllegalVersioningConfigurationException))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -322,15 +322,15 @@ func (n *layer) GetObject(ctx context.Context, p *GetObjectParams) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *layer) checkObject(ctx context.Context, cid *cid.ID, filename string) error {
|
//func (n *layer) checkObject(ctx context.Context, cid *cid.ID, filename string) error {
|
||||||
var err error
|
// var err error
|
||||||
|
//
|
||||||
if _, err = n.objectFindID(ctx, &findParams{cid: cid, val: filename}); err == nil {
|
// if _, err = n.objectFindID(ctx, &findParams{cid: cid, val: filename}); err == nil {
|
||||||
return new(errors.ObjectAlreadyExists)
|
// return new(errors.ObjectAlreadyExists)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
|
|
||||||
// GetObjectInfo returns meta information about the object.
|
// GetObjectInfo returns meta information about the object.
|
||||||
func (n *layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*ObjectInfo, error) {
|
func (n *layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*ObjectInfo, error) {
|
||||||
|
@ -344,7 +344,7 @@ func (n *layer) GetObjectInfo(ctx context.Context, p *HeadObjectParams) (*Object
|
||||||
objInfo, err := n.headLastVersion(ctx, bkt, p.Object)
|
objInfo, err := n.headLastVersion(ctx, bkt, p.Object)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if deleteMark, err2 := strconv.ParseBool(objInfo.Headers[versionsDeleteMarkAttr]); err2 == nil && deleteMark {
|
if deleteMark, err2 := strconv.ParseBool(objInfo.Headers[versionsDeleteMarkAttr]); err2 == nil && deleteMark {
|
||||||
return nil, api.GetAPIError(api.ErrNoSuchKey)
|
return nil, errors.GetAPIError(errors.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return objInfo, err
|
return objInfo, err
|
||||||
|
@ -423,7 +423,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *BucketInfo, obj *Versione
|
||||||
)
|
)
|
||||||
|
|
||||||
versioningEnabled := n.isVersioningEnabled(ctx, bkt)
|
versioningEnabled := n.isVersioningEnabled(ctx, bkt)
|
||||||
if !versioningEnabled && obj.VersionID != "null" && obj.VersionID != "" {
|
if !versioningEnabled && obj.VersionID != unversionedObjectVersionID && obj.VersionID != "" {
|
||||||
return errors.GetAPIError(errors.ErrInvalidVersion)
|
return errors.GetAPIError(errors.ErrInvalidVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,17 +431,17 @@ func (n *layer) deleteObject(ctx context.Context, bkt *BucketInfo, obj *Versione
|
||||||
if len(obj.VersionID) != 0 {
|
if len(obj.VersionID) != 0 {
|
||||||
id := object.NewID()
|
id := object.NewID()
|
||||||
if err := id.Parse(obj.VersionID); err != nil {
|
if err := id.Parse(obj.VersionID); err != nil {
|
||||||
return &errors.DeleteError{Err: api.GetAPIError(api.ErrInvalidVersion), Object: obj.String()}
|
return &errors.DeleteError{Err: errors.GetAPIError(errors.ErrInvalidVersion), Object: obj.String()}
|
||||||
}
|
}
|
||||||
ids = []*object.ID{id}
|
ids = []*object.ID{id}
|
||||||
|
|
||||||
lastObject, err := n.headLastVersion(ctx, bkt, obj.Name)
|
lastObject, err := n.headLastVersion(ctx, bkt, obj.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &api.DeleteError{Err: err, Object: obj.String()}
|
return &errors.DeleteError{Err: err, Object: obj.String()}
|
||||||
}
|
}
|
||||||
if !strings.Contains(lastObject.Headers[versionsAddAttr], obj.VersionID) ||
|
if !strings.Contains(lastObject.Headers[versionsAddAttr], obj.VersionID) ||
|
||||||
strings.Contains(lastObject.Headers[versionsDelAttr], obj.VersionID) {
|
strings.Contains(lastObject.Headers[versionsDelAttr], obj.VersionID) {
|
||||||
return &api.DeleteError{Err: api.GetAPIError(api.ErrInvalidVersion), Object: obj.String()}
|
return &errors.DeleteError{Err: errors.GetAPIError(errors.ErrInvalidVersion), Object: obj.String()}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastObject.ID().String() == obj.VersionID {
|
if lastObject.ID().String() == obj.VersionID {
|
||||||
|
@ -449,7 +449,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *BucketInfo, obj *Versione
|
||||||
addedVersions := strings.Split(added, ",")
|
addedVersions := strings.Split(added, ",")
|
||||||
sourceCopyVersion, err := n.headVersion(ctx, bkt, addedVersions[len(addedVersions)-1])
|
sourceCopyVersion, err := n.headVersion(ctx, bkt, addedVersions[len(addedVersions)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &api.DeleteError{Err: err, Object: obj.String()}
|
return &errors.DeleteError{Err: err, Object: obj.String()}
|
||||||
}
|
}
|
||||||
p := &CopyObjectParams{
|
p := &CopyObjectParams{
|
||||||
SrcObject: sourceCopyVersion,
|
SrcObject: sourceCopyVersion,
|
||||||
|
@ -471,7 +471,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *BucketInfo, obj *Versione
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if _, err := n.objectPut(ctx, bkt, p); err != nil {
|
if _, err := n.objectPut(ctx, bkt, p); err != nil {
|
||||||
return &api.DeleteError{Err: err, Object: obj.String()}
|
return &errors.DeleteError{Err: err, Object: obj.String()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -553,7 +553,7 @@ func (n *layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(ids) != 0 {
|
if len(ids) != 0 {
|
||||||
return api.GetAPIError(api.ErrBucketNotEmpty)
|
return errors.GetAPIError(errors.ErrBucketNotEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.deleteContainer(ctx, bucketInfo.CID)
|
return n.deleteContainer(ctx, bucketInfo.CID)
|
||||||
|
@ -561,11 +561,11 @@ func (n *layer) DeleteBucket(ctx context.Context, p *DeleteBucketParams) error {
|
||||||
|
|
||||||
func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error) {
|
func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsParams) (*ListObjectVersionsInfo, error) {
|
||||||
var (
|
var (
|
||||||
res = ListObjectVersionsInfo{}
|
res = ListObjectVersionsInfo{}
|
||||||
err error
|
err error
|
||||||
bkt *BucketInfo
|
bkt *BucketInfo
|
||||||
ids []*object.ID
|
ids []*object.ID
|
||||||
uniqNames = make(map[string]bool)
|
latest = make(map[string]*ObjectVersionInfo)
|
||||||
)
|
)
|
||||||
|
|
||||||
if bkt, err = n.GetBucketInfo(ctx, p.Bucket); err != nil {
|
if bkt, err = n.GetBucketInfo(ctx, p.Bucket); err != nil {
|
||||||
|
@ -575,9 +575,9 @@ func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsPar
|
||||||
}
|
}
|
||||||
|
|
||||||
versions := make([]*ObjectVersionInfo, 0, len(ids))
|
versions := make([]*ObjectVersionInfo, 0, len(ids))
|
||||||
// todo: deletemarkers is empty now, we will use it after proper realization of versioning
|
|
||||||
deleted := make([]*DeletedObjectInfo, 0, len(ids))
|
deleted := make([]*DeletedObjectInfo, 0, len(ids))
|
||||||
res.DeleteMarker = deleted
|
|
||||||
|
deletedVersions := []string{}
|
||||||
|
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
meta, err := n.objectHead(ctx, bkt.CID, id)
|
meta, err := n.objectHead(ctx, bkt.CID, id)
|
||||||
|
@ -586,45 +586,114 @@ func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsPar
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ov := objectVersionInfoFromMeta(bkt, meta, p.Prefix, p.Delimiter); ov != nil {
|
if ov := objectVersionInfoFromMeta(bkt, meta, p.Prefix, p.Delimiter); ov != nil {
|
||||||
if _, ok := uniqNames[ov.Object.Name]; ok {
|
if ov.Object.Name <= p.KeyMarker {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(p.KeyMarker) > 0 && ov.Object.Name <= p.KeyMarker {
|
if currentLatest, ok := latest[ov.Object.Name]; ok {
|
||||||
continue
|
if less(currentLatest, ov) {
|
||||||
|
latest[ov.Object.Name] = ov
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
latest[ov.Object.Name] = ov
|
||||||
|
}
|
||||||
|
|
||||||
|
if del := ov.Object.Headers[versionsDelAttr]; len(del) != 0 {
|
||||||
|
deletedVersions = append(deletedVersions, strings.Split(del, ",")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed, err := strconv.ParseBool(ov.Object.Headers[versionsDeleteMarkAttr]); err == nil && parsed {
|
||||||
|
deleted = append(deleted, &DeletedObjectInfo{
|
||||||
|
Owner: ov.Object.Owner,
|
||||||
|
Key: ov.Object.Name,
|
||||||
|
VersionID: ov.VersionID,
|
||||||
|
LastModified: ov.Object.Created.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
versions = append(versions, ov)
|
||||||
}
|
}
|
||||||
uniqNames[ov.Object.Name] = ov.Object.isDir
|
|
||||||
versions = append(versions, ov)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(versions, func(i, j int) bool {
|
sort.Slice(versions, func(i, j int) bool {
|
||||||
|
if contains(deletedVersions, versions[i].VersionID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if contains(deletedVersions, versions[j].VersionID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if versions[i].Object.Name == versions[j].Object.Name {
|
||||||
|
if versions[i].CreationEpoch == versions[j].CreationEpoch {
|
||||||
|
return versions[i].VersionID < versions[j].VersionID
|
||||||
|
}
|
||||||
|
return versions[i].CreationEpoch < versions[j].CreationEpoch
|
||||||
|
}
|
||||||
return versions[i].Object.Name < versions[j].Object.Name
|
return versions[i].Object.Name < versions[j].Object.Name
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(versions) > p.MaxKeys {
|
for i, objVersion := range versions {
|
||||||
res.IsTruncated = true
|
if i == len(versions)-1 || objVersion.Object.Name != versions[i+1].Object.Name {
|
||||||
|
objVersion.IsLatest = true
|
||||||
lastVersion := versions[p.MaxKeys-1]
|
|
||||||
res.KeyMarker = lastVersion.Object.Name
|
|
||||||
res.VersionIDMarker = lastVersion.VersionID
|
|
||||||
|
|
||||||
nextVersion := versions[p.MaxKeys]
|
|
||||||
res.NextKeyMarker = nextVersion.Object.Name
|
|
||||||
res.NextVersionIDMarker = nextVersion.VersionID
|
|
||||||
|
|
||||||
versions = versions[:p.MaxKeys]
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ov := range versions {
|
|
||||||
if isDir := uniqNames[ov.Object.Name]; isDir {
|
|
||||||
res.CommonPrefixes = append(res.CommonPrefixes, &ov.Object.Name)
|
|
||||||
} else {
|
|
||||||
res.Version = append(res.Version, ov)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i, objVersion := range versions {
|
||||||
|
if !contains(deletedVersions, objVersion.VersionID) {
|
||||||
|
versions = versions[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, objVersion := range deleted {
|
||||||
|
if !contains(deletedVersions, objVersion.VersionID) {
|
||||||
|
deleted = deleted[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//if len(versions) > p.MaxKeys {
|
||||||
|
// res.IsTruncated = true
|
||||||
|
//
|
||||||
|
// lastVersion := versions[p.MaxKeys-1]
|
||||||
|
// res.KeyMarker = lastVersion.Object.Name
|
||||||
|
// res.VersionIDMarker = lastVersion.VersionID
|
||||||
|
//
|
||||||
|
// nextVersion := versions[p.MaxKeys]
|
||||||
|
// res.NextKeyMarker = nextVersion.Object.Name
|
||||||
|
// res.NextVersionIDMarker = nextVersion.VersionID
|
||||||
|
//
|
||||||
|
// versions = versions[:p.MaxKeys]
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//for _, ov := range versions {
|
||||||
|
// if isDir := uniqNames[ov.Object.Name]; isDir {
|
||||||
|
// res.CommonPrefixes = append(res.CommonPrefixes, &ov.Object.Name)
|
||||||
|
// } else {
|
||||||
|
// res.Version = append(res.Version, ov)
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
res.Version = versions
|
||||||
|
res.DeleteMarker = deleted
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func less(ov1, ov2 *ObjectVersionInfo) bool {
|
||||||
|
if ov1.CreationEpoch == ov2.CreationEpoch {
|
||||||
|
return ov1.VersionID < ov2.VersionID
|
||||||
|
}
|
||||||
|
return ov1.CreationEpoch < ov2.CreationEpoch
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(list []string, elem string) bool {
|
||||||
|
for _, item := range list {
|
||||||
|
if elem == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (n *layer) PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*ObjectInfo, error) {
|
func (n *layer) PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*ObjectInfo, error) {
|
||||||
bucketInfo, err := n.GetBucketInfo(ctx, p.Bucket)
|
bucketInfo, err := n.GetBucketInfo(ctx, p.Bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -252,7 +252,7 @@ func (n *layer) headLastVersion(ctx context.Context, bkt *BucketInfo, objectName
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return nil, api.GetAPIError(api.ErrNoSuchKey)
|
return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
infos := make([]*object.Object, 0, len(ids))
|
infos := make([]*object.Object, 0, len(ids))
|
||||||
|
@ -284,7 +284,7 @@ func (n *layer) headVersion(ctx context.Context, bkt *BucketInfo, versionID stri
|
||||||
meta, err := n.objectHead(ctx, bkt.CID, oid)
|
meta, err := n.objectHead(ctx, bkt.CID, oid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "not found") {
|
if strings.Contains(err.Error(), "not found") {
|
||||||
return nil, api.GetAPIError(api.ErrNoSuchVersion)
|
return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchVersion)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,9 +54,10 @@ type (
|
||||||
|
|
||||||
// ObjectVersionInfo stores info about objects versions.
|
// ObjectVersionInfo stores info about objects versions.
|
||||||
ObjectVersionInfo struct {
|
ObjectVersionInfo struct {
|
||||||
Object *ObjectInfo
|
Object *ObjectInfo
|
||||||
IsLatest bool
|
IsLatest bool
|
||||||
VersionID string
|
VersionID string
|
||||||
|
CreationEpoch uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeletedObjectInfo stores info about deleted versions of objects.
|
// DeletedObjectInfo stores info about deleted versions of objects.
|
||||||
|
@ -156,7 +157,7 @@ func objectVersionInfoFromMeta(bkt *BucketInfo, meta *object.Object, prefix, del
|
||||||
if oi == nil {
|
if oi == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &ObjectVersionInfo{Object: oi, IsLatest: true, VersionID: unversionedObjectVersionID}
|
return &ObjectVersionInfo{Object: oi, VersionID: meta.ID().String(), CreationEpoch: meta.CreationEpoch()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filenameFromObject(o *object.Object) string {
|
func filenameFromObject(o *object.Object) string {
|
||||||
|
|
Loading…
Reference in a new issue