Skip to content

Commit

Permalink
private/object: add StatObject to get specific object version
Browse files Browse the repository at this point in the history
New method to stat not only latest version of object but also
other existing versions.

storj/storj#6221

Change-Id: Icab5959f533463cab80e5ef20baaa1b725443a82
  • Loading branch information
mniewrzal committed Nov 8, 2023
1 parent 61e7655 commit fbde9cd
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 34 deletions.
2 changes: 1 addition & 1 deletion object.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (project *Project) StatObject(ctx context.Context, bucket, key string) (inf
}
defer func() { err = errs.Combine(err, db.Close()) }()

obj, err := db.GetObject(ctx, bucket, key)
obj, err := db.GetObject(ctx, bucket, key, nil)
if err != nil {
return nil, convertKnownErrors(err, bucket, key)
}
Expand Down
14 changes: 5 additions & 9 deletions private/metaclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ func (client *Client) CommitObject(ctx context.Context, params CommitObjectParam
type GetObjectParams struct {
Bucket []byte
EncryptedObjectKey []byte
Version int32
Version []byte

RedundancySchemePerSegment bool
}
Expand All @@ -524,7 +524,7 @@ func (params *GetObjectParams) toRequest(header *pb.RequestHeader) *pb.ObjectGet
Header: header,
Bucket: params.Bucket,
EncryptedObjectKey: params.EncryptedObjectKey,
Version: params.Version,
ObjectVersion: params.Version,
RedundancySchemePerSegment: params.RedundancySchemePerSegment,
}
}
Expand Down Expand Up @@ -557,7 +557,7 @@ func newObjectInfo(object *pb.Object) RawObjectItem {
info := RawObjectItem{
Bucket: string(object.Bucket),
EncryptedObjectKey: object.EncryptedObjectKey,
Version: uint32(object.Version),
Version: object.ObjectVersion,

StreamID: object.StreamId,

Expand Down Expand Up @@ -613,7 +613,6 @@ func (client *Client) GetObject(ctx context.Context, params GetObjectParams) (_
type GetObjectIPsParams struct {
Bucket []byte
EncryptedObjectKey []byte
Version int32
}

// GetObjectIPsResponse is the response from GetObjectIPs.
Expand All @@ -629,7 +628,6 @@ func (params *GetObjectIPsParams) toRequest(header *pb.RequestHeader) *pb.Object
Header: header,
Bucket: params.Bucket,
EncryptedObjectKey: params.EncryptedObjectKey,
Version: params.Version,
}
}

Expand Down Expand Up @@ -661,7 +659,6 @@ func (client *Client) GetObjectIPs(ctx context.Context, params GetObjectIPsParam
type UpdateObjectMetadataParams struct {
Bucket []byte
EncryptedObjectKey []byte
Version int32
StreamID storj.StreamID

EncryptedMetadataNonce storj.Nonce
Expand All @@ -674,7 +671,6 @@ func (params *UpdateObjectMetadataParams) toRequest(header *pb.RequestHeader) *p
Header: header,
Bucket: params.Bucket,
EncryptedObjectKey: params.EncryptedObjectKey,
Version: params.Version,
StreamId: params.StreamID,
EncryptedMetadataNonce: params.EncryptedMetadataNonce,
EncryptedMetadata: params.EncryptedMetadata,
Expand Down Expand Up @@ -811,7 +807,7 @@ func newListObjectsResponse(response *pb.ObjectListResponse, encryptedPrefix []b

objects[i] = RawObjectListItem{
EncryptedObjectKey: object.EncryptedObjectKey,
Version: object.Version,
Version: object.ObjectVersion,
Status: int32(object.Status),
StatusAt: object.StatusAt,
CreatedAt: object.CreatedAt,
Expand Down Expand Up @@ -891,7 +887,7 @@ func newListPendingObjectStreamsResponse(response *pb.ObjectListPendingStreamsRe

objects[i] = RawObjectListItem{
EncryptedObjectKey: object.EncryptedObjectKey,
Version: object.Version,
Version: object.ObjectVersion,
Status: int32(object.Status),
StatusAt: object.StatusAt,
CreatedAt: object.CreatedAt,
Expand Down
6 changes: 3 additions & 3 deletions private/metaclient/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ func (db *DB) UpdateObjectMetadata(ctx context.Context, bucket, key string, newM
return db.metainfo.UpdateObjectMetadata(ctx, UpdateObjectMetadataParams{
Bucket: []byte(bucket),
EncryptedObjectKey: []byte(encPath.Raw()),
Version: int32(object.Version),
StreamID: object.Stream.ID,
EncryptedMetadata: streamMetaBytes,
EncryptedMetadataEncryptedKey: encryptedKey,
Expand Down Expand Up @@ -516,7 +515,7 @@ func (db *DB) ListSegments(ctx context.Context, params ListSegmentsParams) (resp
}

// GetObject returns information about an object.
func (db *DB) GetObject(ctx context.Context, bucket, key string) (info Object, err error) {
func (db *DB) GetObject(ctx context.Context, bucket, key string, version []byte) (info Object, err error) {
defer mon.Task()(&ctx)(&err)

if bucket == "" {
Expand All @@ -535,6 +534,7 @@ func (db *DB) GetObject(ctx context.Context, bucket, key string) (info Object, e
objectInfo, err := db.metainfo.GetObject(ctx, GetObjectParams{
Bucket: []byte(bucket),
EncryptedObjectKey: []byte(encPath.Raw()),
Version: version,
RedundancySchemePerSegment: true,
})
if err != nil {
Expand Down Expand Up @@ -605,7 +605,7 @@ func (db *DB) ObjectFromRawObjectItem(ctx context.Context, bucket, key string, o

func (db *DB) objectFromRawObjectListItem(bucket string, path storj.Path, listItem RawObjectListItem, stream *pb.StreamInfo, streamMeta pb.StreamMeta) (Object, error) {
object := Object{
Version: uint32(listItem.Version),
Version: listItem.Version,
Bucket: Bucket{Name: bucket},
Path: path,
IsPrefix: listItem.IsPrefix,
Expand Down
6 changes: 3 additions & 3 deletions private/metaclient/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (

// RawObjectItem represents raw object item from get request.
type RawObjectItem struct {
Version uint32
Version []byte
Bucket string
EncryptedObjectKey []byte

Expand All @@ -37,7 +37,7 @@ type RawObjectItem struct {
// RawObjectListItem represents raw object item from list objects request.
type RawObjectListItem struct {
EncryptedObjectKey []byte
Version int32
Version []byte
Status int32
CreatedAt time.Time
StatusAt time.Time
Expand Down Expand Up @@ -93,7 +93,7 @@ var (

// Object contains information about a specific object.
type Object struct {
Version uint32
Version []byte
Bucket Bucket
Path string
IsPrefix bool
Expand Down
54 changes: 54 additions & 0 deletions private/object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,29 @@ import (
"context"
_ "unsafe" // for go:linkname

"github.com/spacemonkeygo/monkit/v3"
"github.com/zeebo/errs"

"storj.io/uplink"
"storj.io/uplink/internal/expose"
"storj.io/uplink/private/metaclient"
)

var mon = monkit.Package()

// Error is default error class for uplink.
var packageError = errs.Class("object")

// IPSummary contains information about the object IP-s.
type IPSummary = metaclient.GetObjectIPsResponse

// VersionedObject represents object with version.
// TODO find better place of name for this and related things.
type VersionedObject struct {
uplink.Object
Version []byte
}

// GetObjectIPs returns the IP-s for a given object.
//
// TODO: delete, once we have stopped using it.
Expand Down Expand Up @@ -50,3 +60,47 @@ func GetObjectIPSummary(ctx context.Context, config uplink.Config, access *uplin
summary, err := db.GetObjectIPs(ctx, metaclient.Bucket{Name: bucket}, key)
return summary, packageError.Wrap(err)
}

// StatObject returns information about an object at the specific key and version.
func StatObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte) (info *VersionedObject, err error) {
defer mon.Task()(&ctx)(&err)

db, err := dialMetainfoDB(ctx, project)
if err != nil {
return nil, convertKnownErrors(err, bucket, key)
}
defer func() { err = errs.Combine(err, db.Close()) }()

obj, err := db.GetObject(ctx, bucket, key, version)
if err != nil {
return nil, convertKnownErrors(err, bucket, key)
}

return convertObject(&obj), nil
}

// convertObject converts metainfo.Object to Version.
func convertObject(obj *metaclient.Object) *VersionedObject {
if obj.Bucket.Name == "" { // zero object
return nil
}

return &VersionedObject{
Object: uplink.Object{
Key: obj.Path,
System: uplink.SystemMetadata{
Created: obj.Created,
Expires: obj.Expires,
ContentLength: obj.Size,
},
Custom: obj.Metadata,
},
Version: obj.Version,
}
}

//go:linkname convertKnownErrors storj.io/uplink.convertKnownErrors
func convertKnownErrors(err error, bucket, key string) error

//go:linkname dialMetainfoDB storj.io/uplink.dialMetainfoDB
func dialMetainfoDB(ctx context.Context, project *uplink.Project) (_ *metaclient.DB, err error)
65 changes: 65 additions & 0 deletions testsuite/private/object/object_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

package object_test

import (
"testing"

"github.com/stretchr/testify/require"

"storj.io/common/memory"
"storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/storj/private/testplanet"
"storj.io/uplink"
"storj.io/uplink/private/object"
)

func TestStatObject(t *testing.T) {
testplanet.Run(t, testplanet.Config{
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
bucketName := "test-bucket"
objectKey := "test-object"
err := planet.Uplinks[0].CreateBucket(ctx, planet.Satellites[0], bucketName)
require.NoError(t, err)

project, err := planet.Uplinks[0].OpenProject(ctx, planet.Satellites[0])
require.NoError(t, err)
defer ctx.Check(project.Close)

_, err = object.StatObject(ctx, project, "", "", nil)
require.ErrorIs(t, err, uplink.ErrBucketNameInvalid)

_, err = object.StatObject(ctx, project, bucketName, "", nil)
require.ErrorIs(t, err, uplink.ErrObjectKeyInvalid)

_, err = object.StatObject(ctx, project, "non-existing-bucket", objectKey, nil)
require.ErrorIs(t, err, uplink.ErrObjectNotFound)

_, err = object.StatObject(ctx, project, bucketName, "non-existing-object", nil)
require.ErrorIs(t, err, uplink.ErrObjectNotFound)

err = planet.Uplinks[0].Upload(ctx, planet.Satellites[0], bucketName, objectKey, testrand.Bytes(memory.KiB))
require.NoError(t, err)

obj, err := object.StatObject(ctx, project, bucketName, objectKey, nil)
require.NoError(t, err)
require.Equal(t, objectKey, obj.Key)
require.NotZero(t, obj.Version)

// try to stat specific version
objTwo, err := object.StatObject(ctx, project, bucketName, objectKey, obj.Version)
require.NoError(t, err)
require.Equal(t, objectKey, objTwo.Key)
require.Equal(t, obj.Version, objTwo.Version)

// try to stat NOT EXISTING version
_, err = object.StatObject(ctx, project, bucketName, objectKey, []byte{1, 2, 3, 4, 5, 6, 7, 8})
require.ErrorIs(t, err, uplink.ErrObjectNotFound)
})
}

// TODO(ver) add tests for versioned/unversioned/suspended objects as well as delete markers
// for all methods from 'object' package
26 changes: 8 additions & 18 deletions testsuite/private/testuplink/objects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,23 @@ func TestGetObject(t *testing.T) {
require.NoError(t, err)
upload(ctx, t, db, streams, bucket.Name, TestFile, nil)

_, err = db.GetObject(ctx, "", "")
_, err = db.GetObject(ctx, "", "", nil)
assert.True(t, metaclient.ErrNoBucket.Has(err))

_, err = db.GetObject(ctx, bucket.Name, "")
_, err = db.GetObject(ctx, bucket.Name, "", nil)
assert.True(t, metaclient.ErrNoPath.Has(err))

_, err = db.GetObject(ctx, "non-existing-bucket", TestFile)
_, err = db.GetObject(ctx, "non-existing-bucket", TestFile, nil)
assert.True(t, metaclient.ErrObjectNotFound.Has(err))

_, err = db.GetObject(ctx, bucket.Name, "non-existing-file")
_, err = db.GetObject(ctx, bucket.Name, "non-existing-file", nil)
assert.True(t, metaclient.ErrObjectNotFound.Has(err))

object, err := db.GetObject(ctx, bucket.Name, TestFile)
object, err := db.GetObject(ctx, bucket.Name, TestFile, nil)
require.NoError(t, err)
assert.Equal(t, TestFile, object.Path)
assert.Equal(t, TestBucket, object.Bucket.Name)
assert.Equal(t, uint32(1), object.Version)
assert.NotNil(t, uint32(1), object.Version)
})
}

Expand All @@ -95,10 +95,10 @@ func TestDownloadObject(t *testing.T) {
upload(ctx, t, db, streams, bucket.Name, "small-file", []byte("test"))
upload(ctx, t, db, streams, bucket.Name, "large-file", data)

_, err = db.GetObject(ctx, "", "")
_, err = db.GetObject(ctx, "", "", nil)
assert.True(t, metaclient.ErrNoBucket.Has(err))

_, err = db.GetObject(ctx, bucket.Name, "")
_, err = db.GetObject(ctx, bucket.Name, "", nil)
assert.True(t, metaclient.ErrNoPath.Has(err))

assertData(ctx, t, db, streams, bucket.Name, "empty-file", []byte{})
Expand Down Expand Up @@ -422,11 +422,6 @@ func TestListObjects(t *testing.T) {
for i, item := range list.Items {
assert.Equal(t, tt.result[i], item.Path, errTag)
assert.Equal(t, TestBucket, item.Bucket.Name, errTag)
if item.IsPrefix {
assert.Equal(t, uint32(0), item.Version, errTag)
} else {
assert.Equal(t, uint32(1), item.Version, errTag)
}
}
}
}
Expand Down Expand Up @@ -484,11 +479,6 @@ func TestListObjects_PagingWithDiffPassphrase(t *testing.T) {
for i, item := range list.Items {
assert.Equal(t, tt.result[i], item.Path, errTag)
assert.Equal(t, TestBucket, item.Bucket.Name, errTag)
if item.IsPrefix {
assert.Equal(t, uint32(0), item.Version, errTag)
} else {
assert.Equal(t, uint32(1), item.Version, errTag)
}
}
}
}
Expand Down

0 comments on commit fbde9cd

Please sign in to comment.