gcs: Use Pager to iterate results in ListObjectsV1/V2 (#6162)

Fixes #6052
master
wd256 6 years ago committed by Nitish Tiwari
parent 6c93c60424
commit 3ec4738955
  1. 148
      cmd/gateway/gcs/gateway-gcs.go

@ -554,16 +554,16 @@ func isGCSMarker(marker string) bool {
// ListObjects - lists all blobs in GCS bucket filtered by prefix // ListObjects - lists all blobs in GCS bucket filtered by prefix
func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) { func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (minio.ListObjectsInfo, error) {
if maxKeys == 0 {
return minio.ListObjectsInfo{}, nil
}
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{ it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
Delimiter: delimiter, Delimiter: delimiter,
Prefix: prefix, Prefix: prefix,
Versions: false, Versions: false,
}) })
isTruncated := false
nextMarker := ""
prefixes := []string{}
// To accommodate S3-compatible applications using // To accommodate S3-compatible applications using
// ListObjectsV1 to use object keys as markers to control the // ListObjectsV1 to use object keys as markers to control the
// listing of objects, we use the following encoding scheme to // listing of objects, we use the following encoding scheme to
@ -574,45 +574,40 @@ func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix stri
// prefixing "{minio}" to the GCS continuation token, // prefixing "{minio}" to the GCS continuation token,
// e.g, "{minio}CgRvYmoz" // e.g, "{minio}CgRvYmoz"
// //
// - Application supplied markers are used as-is to list // - Application supplied markers are transformed to a
// object keys that appear after it in the lexicographical order. // GCS continuation token.
// If application is using GCS continuation token we should // If application is using GCS continuation token we should
// strip the gcsTokenPrefix we added. // strip the gcsTokenPrefix we added.
gcsMarker := isGCSMarker(marker) token := ""
if gcsMarker { if marker != "" {
it.PageInfo().Token = strings.TrimPrefix(marker, gcsTokenPrefix) if isGCSMarker(marker) {
token = strings.TrimPrefix(marker, gcsTokenPrefix)
} else {
token = toGCSPageToken(marker)
} }
it.PageInfo().MaxSize = maxKeys
objects := []minio.ObjectInfo{}
for {
if len(objects) >= maxKeys {
// check if there is one next object and
// if that one next object is our hidden
// metadata folder, then just break
// otherwise we've truncated the output
attrs, _ := it.Next()
if attrs != nil && attrs.Prefix == minio.GatewayMinioSysTmp {
break
} }
nextMarker := ""
isTruncated = true var prefixes []string
break var objects []minio.ObjectInfo
} var nextPageToken string
var err error
attrs, err := it.Next() pager := iterator.NewPager(it, maxKeys, token)
if err == iterator.Done { for {
break gcsObjects := make([]*storage.ObjectAttrs, 0)
} nextPageToken, err = pager.NextPage(&gcsObjects)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return minio.ListObjectsInfo{}, gcsToObjectError(err, bucket, prefix) return minio.ListObjectsInfo{}, gcsToObjectError(err, bucket, prefix)
} }
nextMarker = toGCSPageToken(attrs.Name) for _, attrs := range gcsObjects {
// Due to minio.GatewayMinioSysTmp keys being skipped, the number of objects + prefixes
// returned may not total maxKeys. This behavior is compatible with the S3 spec which
// allows the response to include less keys than maxKeys.
if attrs.Prefix == minio.GatewayMinioSysTmp { if attrs.Prefix == minio.GatewayMinioSysTmp {
// We don't return our metadata prefix. // We don't return our metadata prefix.
continue continue
@ -626,31 +621,39 @@ func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix stri
continue continue
} }
} }
if attrs.Prefix != "" { if attrs.Prefix != "" {
prefixes = append(prefixes, attrs.Prefix) prefixes = append(prefixes, attrs.Prefix)
continue } else {
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
}
// The NextMarker property should only be set in the response if a delimiter is used
if delimiter != "" {
if attrs.Prefix > nextMarker {
nextMarker = attrs.Prefix
} else if attrs.Name > nextMarker {
nextMarker = attrs.Name
}
} }
if !gcsMarker && attrs.Name <= marker {
// if user supplied a marker don't append
// objects until we reach marker (and skip it).
continue
} }
objects = append(objects, minio.ObjectInfo{ // Exit the loop if at least one item can be returned from
Name: attrs.Name, // the current page or there are no more pages available
Bucket: attrs.Bucket, if nextPageToken == "" || len(prefixes)+len(objects) > 0 {
ModTime: attrs.Updated, break
Size: attrs.Size, }
ETag: minio.ToS3ETag(fmt.Sprintf("%d", attrs.CRC32C)), }
UserDefined: attrs.Metadata,
ContentType: attrs.ContentType, if nextPageToken == "" {
ContentEncoding: attrs.ContentEncoding, nextMarker = ""
}) } else if nextMarker != "" {
nextMarker = gcsTokenPrefix + toGCSPageToken(nextMarker)
} }
return minio.ListObjectsInfo{ return minio.ListObjectsInfo{
IsTruncated: isTruncated, IsTruncated: nextPageToken != "",
NextMarker: gcsTokenPrefix + nextMarker, NextMarker: nextMarker,
Prefixes: prefixes, Prefixes: prefixes,
Objects: objects, Objects: objects,
}, nil }, nil
@ -658,8 +661,8 @@ func (l *gcsGateway) ListObjects(ctx context.Context, bucket string, prefix stri
// ListObjectsV2 - lists all blobs in GCS bucket filtered by prefix // ListObjectsV2 - lists all blobs in GCS bucket filtered by prefix
func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) { func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (minio.ListObjectsV2Info, error) {
if continuationToken == "" && startAfter != "" { if maxKeys == 0 {
continuationToken = toGCSPageToken(startAfter) return minio.ListObjectsV2Info{ContinuationToken: continuationToken}, nil
} }
it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{ it := l.client.Bucket(bucket).Objects(l.ctx, &storage.Query{
@ -668,35 +671,30 @@ func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continua
Versions: false, Versions: false,
}) })
isTruncated := false token := continuationToken
if token == "" && startAfter != "" {
if continuationToken != "" { token = toGCSPageToken(startAfter)
// If client sends continuationToken, set it
it.PageInfo().Token = continuationToken
} else {
// else set the continuationToken to return
continuationToken = it.PageInfo().Token
if continuationToken != "" {
// If GCS SDK sets continuationToken, it means there are more than maxKeys in the current page
// and the response will be truncated
isTruncated = true
}
} }
var prefixes []string var prefixes []string
var objects []minio.ObjectInfo var objects []minio.ObjectInfo
var nextPageToken string
var err error
for keyCount := 0; keyCount < maxKeys; keyCount++ { pager := iterator.NewPager(it, maxKeys, token)
attrs, err := it.Next() for {
if err == iterator.Done { gcsObjects := make([]*storage.ObjectAttrs, 0)
break nextPageToken, err = pager.NextPage(&gcsObjects)
}
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return minio.ListObjectsV2Info{}, gcsToObjectError(err, bucket, prefix) return minio.ListObjectsV2Info{}, gcsToObjectError(err, bucket, prefix)
} }
for _, attrs := range gcsObjects {
// Due to minio.GatewayMinioSysTmp keys being skipped, the number of objects + prefixes
// returned may not total maxKeys. This behavior is compatible with the S3 spec which
// allows the response to include less keys than maxKeys.
if attrs.Prefix == minio.GatewayMinioSysTmp { if attrs.Prefix == minio.GatewayMinioSysTmp {
// We don't return our metadata prefix. // We don't return our metadata prefix.
continue continue
@ -713,16 +711,22 @@ func (l *gcsGateway) ListObjectsV2(ctx context.Context, bucket, prefix, continua
if attrs.Prefix != "" { if attrs.Prefix != "" {
prefixes = append(prefixes, attrs.Prefix) prefixes = append(prefixes, attrs.Prefix)
continue } else {
objects = append(objects, fromGCSAttrsToObjectInfo(attrs))
}
} }
objects = append(objects, fromGCSAttrsToObjectInfo(attrs)) // Exit the loop if at least one item can be returned from
// the current page or there are no more pages available
if nextPageToken == "" || len(prefixes)+len(objects) > 0 {
break
}
} }
return minio.ListObjectsV2Info{ return minio.ListObjectsV2Info{
IsTruncated: isTruncated, IsTruncated: nextPageToken != "",
ContinuationToken: continuationToken, ContinuationToken: continuationToken,
NextContinuationToken: continuationToken, NextContinuationToken: nextPageToken,
Prefixes: prefixes, Prefixes: prefixes,
Objects: objects, Objects: objects,
}, nil }, nil

Loading…
Cancel
Save