Skip to content

Commit d148529

Browse files
committed
fix: Posix compatibility with Tigris backend
1 parent 3573475 commit d148529

File tree

14 files changed

+382
-135
lines changed

14 files changed

+382
-135
lines changed

core/backend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ type BlobItemOutput struct {
4242
LastModified *time.Time
4343
Size uint64
4444
StorageClass *string
45-
// may be nil in list responses for backends that don't return metadata in listings
46-
Metadata map[string]*string
45+
Metadata map[string]*string
46+
Content []byte
4747
}
4848

4949
type HeadBlobOutput struct {

core/backend_s3.go

Lines changed: 238 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616
package core
1717

1818
import (
19-
"github.com/yandex-cloud/geesefs/core/cfg"
20-
"golang.org/x/sync/errgroup"
21-
19+
"bytes"
20+
"encoding/base64"
2221
"encoding/json"
22+
"encoding/xml"
2323
"errors"
2424
"fmt"
25+
"io"
2526
"io/ioutil"
2627
"net/http"
2728
"net/url"
@@ -39,6 +40,8 @@ import (
3940
"github.com/aws/aws-sdk-go/aws/credentials"
4041
"github.com/aws/aws-sdk-go/aws/request"
4142
"github.com/aws/aws-sdk-go/service/s3"
43+
"github.com/yandex-cloud/geesefs/core/cfg"
44+
"golang.org/x/sync/errgroup"
4245
)
4346

4447
type S3Backend struct {
@@ -437,83 +440,179 @@ func withHeader(req *request.Request, key, value string) {
437440
req.HTTPRequest.Header.Set(key, value)
438441
}
439442

440-
func (s *S3Backend) ListObjectsV2(params *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, string, error) {
441-
/*
442-
if s.config.ListV1Ext {
443-
in := s3.ListObjectsV1ExtInput(*params)
444-
req, resp := s.S3.ListObjectsV1ExtRequest(&in)
445-
if s.flags.TigrisPrefetch {
446-
withHeader(req, "X-Tigris-Prefetch", "true")
447-
}
448-
err := req.Send()
449-
if err != nil {
450-
if awsErr, ok := err.(awserr.Error); ok {
451-
if awsErr.Code() == "InvalidArgument" || awsErr.Code() == "NotImplemented" {
452-
// Fallback to list v1
453-
s.config.ListV1Ext = false
454-
return s.ListObjectsV2(params)
455-
}
456-
}
457-
return nil, "", err
458-
}
459-
out := s3.ListObjectsV2Output(*resp)
460-
for _, obj := range out.Contents {
461-
// Make non-nil maps for all objects so that we know metadata is empty
462-
if obj.UserMetadata == nil {
463-
obj.UserMetadata = make(map[string]*string)
464-
}
465-
}
466-
return &out, s.getRequestId(req), nil
467-
} else
468-
*/
443+
func (s *S3Backend) listObjects(params *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, string, error) {
469444
if s.config.ListV2 {
470-
req, resp := s.S3.ListObjectsV2Request(params)
471-
if s.flags.TigrisPrefetch {
472-
withHeader(req, "X-Tigris-Prefetch", "true")
473-
}
474-
err := req.Send()
475-
if err != nil {
476-
return nil, "", err
477-
}
478-
return resp, s.getRequestId(req), nil
445+
return s.listObjectsV2(params)
446+
}
447+
return s.listObjectsV1(params)
448+
}
449+
450+
func (s *S3Backend) listObjectsV2(params *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, string, error) {
451+
req, resp := s.S3.ListObjectsV2Request(params)
452+
if s.flags.TigrisPrefetch {
453+
withHeader(req, "X-Tigris-Prefetch", "true")
454+
}
455+
err := req.Send()
456+
if err != nil {
457+
return nil, "", err
458+
}
459+
return resp, s.getRequestId(req), nil
460+
}
461+
462+
func (s *S3Backend) listObjectsV1(params *s3.ListObjectsV2Input) (*s3.ListObjectsV2Output, string, error) {
463+
v1 := s3.ListObjectsInput{
464+
Bucket: params.Bucket,
465+
Delimiter: params.Delimiter,
466+
EncodingType: params.EncodingType,
467+
MaxKeys: params.MaxKeys,
468+
Prefix: params.Prefix,
469+
RequestPayer: params.RequestPayer,
470+
}
471+
if params.StartAfter != nil {
472+
v1.Marker = params.StartAfter
479473
} else {
480-
v1 := s3.ListObjectsInput{
481-
Bucket: params.Bucket,
482-
Delimiter: params.Delimiter,
483-
EncodingType: params.EncodingType,
484-
MaxKeys: params.MaxKeys,
485-
Prefix: params.Prefix,
486-
RequestPayer: params.RequestPayer,
487-
}
488-
if params.StartAfter != nil {
489-
v1.Marker = params.StartAfter
490-
} else {
491-
v1.Marker = params.ContinuationToken
492-
}
474+
v1.Marker = params.ContinuationToken
475+
}
493476

494-
objs, err := s.S3.ListObjects(&v1)
495-
if err != nil {
496-
return nil, "", err
497-
}
477+
objs, err := s.S3.ListObjects(&v1)
478+
if err != nil {
479+
return nil, "", err
480+
}
481+
482+
count := int64(len(objs.Contents))
483+
v2Objs := s3.ListObjectsV2Output{
484+
CommonPrefixes: objs.CommonPrefixes,
485+
Contents: objs.Contents,
486+
ContinuationToken: objs.Marker,
487+
Delimiter: objs.Delimiter,
488+
EncodingType: objs.EncodingType,
489+
IsTruncated: objs.IsTruncated,
490+
KeyCount: &count,
491+
MaxKeys: objs.MaxKeys,
492+
Name: objs.Name,
493+
NextContinuationToken: objs.NextMarker,
494+
Prefix: objs.Prefix,
495+
StartAfter: objs.Marker,
496+
}
497+
498+
return &v2Objs, "", nil
499+
}
498500

499-
count := int64(len(objs.Contents))
500-
v2Objs := s3.ListObjectsV2Output{
501-
CommonPrefixes: objs.CommonPrefixes,
502-
Contents: objs.Contents,
503-
ContinuationToken: objs.Marker,
504-
Delimiter: objs.Delimiter,
505-
EncodingType: objs.EncodingType,
506-
IsTruncated: objs.IsTruncated,
507-
KeyCount: &count,
508-
MaxKeys: objs.MaxKeys,
509-
Name: objs.Name,
510-
NextContinuationToken: objs.NextMarker,
511-
Prefix: objs.Prefix,
512-
StartAfter: objs.Marker,
513-
}
501+
type RestoreStatus struct {
502+
IsRestoreInProgress *bool `type:"boolean"`
503+
RestoreExpiryDate *time.Time `type:"timestamp"`
504+
}
505+
506+
type Owner struct {
507+
DisplayName *string `type:"string"`
508+
ID *string `type:"string"`
509+
}
510+
511+
type CommonPrefix struct {
512+
Prefix *string `type:"string"`
513+
}
514+
515+
type xsdBase64Binary []byte
516+
517+
func (b *xsdBase64Binary) UnmarshalText(text []byte) (err error) {
518+
*b, err = base64.StdEncoding.DecodeString(string(text))
519+
return
520+
}
521+
func (b xsdBase64Binary) MarshalText() ([]byte, error) {
522+
var buf bytes.Buffer
523+
enc := base64.NewEncoder(base64.StdEncoding, &buf)
524+
if _, err := enc.Write([]byte(b)); err != nil {
525+
return nil, err
526+
}
527+
if err := enc.Close(); err != nil {
528+
return nil, err
529+
}
530+
return buf.Bytes(), nil
531+
}
532+
533+
type MetadataEntry struct {
534+
Name string
535+
Value string
536+
}
537+
538+
type Object struct {
539+
ChecksumAlgorithm []*string `type:"list" flattened:"true" enum:"ChecksumAlgorithm"`
540+
ETag *string `type:"string"`
541+
Key *string `min:"1" type:"string"`
542+
LastModified *time.Time `type:"timestamp"`
543+
Owner *Owner `type:"structure"`
544+
RestoreStatus *RestoreStatus `type:"structure"`
545+
Size *int64 `type:"long"`
546+
StorageClass *string `type:"string" enum:"ObjectStorageClass"`
547+
Metadata []MetadataEntry `type:"list" flattened:"true"`
548+
Data xsdBase64Binary `type:"blob"`
549+
}
550+
551+
type ListObjectsV2Output struct {
552+
_ struct{} `type:"structure"`
553+
CommonPrefixes []*CommonPrefix `type:"list" flattened:"true"`
554+
Contents []*Object `type:"list" flattened:"true"`
555+
ContinuationToken *string `type:"string"`
556+
Delimiter *string `type:"string"`
557+
EncodingType *string `type:"string" enum:"EncodingType"`
558+
IsTruncated *bool `type:"boolean"`
559+
KeyCount *int64 `type:"integer"`
560+
MaxKeys *int64 `type:"integer"`
561+
Name *string `type:"string"`
562+
NextContinuationToken *string `type:"string"`
563+
Prefix *string `type:"string"`
564+
RequestCharged *string `location:"header" locationName:"x-amz-request-charged" type:"string" enum:"RequestCharged"`
565+
StartAfter *string `type:"string"`
566+
}
567+
568+
func unmarshalListObjectsV2Response(r *request.Request) {
569+
if r.Error != nil {
570+
return
571+
}
572+
573+
b, err := io.ReadAll(r.HTTPResponse.Body)
574+
if err != nil {
575+
r.Error = fmt.Errorf("custom unmarshal read body: %w", err)
576+
return
577+
}
578+
579+
var response *ListObjectsV2Output
580+
if err := xml.Unmarshal(b, &response); err != nil {
581+
r.Error = fmt.Errorf("custom unmarshal failed: %w", err)
582+
return
583+
}
584+
585+
r.Data = response
586+
}
587+
588+
func (s *S3Backend) listObjectsV2Special(params *s3.ListObjectsV2Input) (*ListBlobsOutput, error) {
589+
req, _ := s.S3.ListObjectsV2Request(params)
590+
591+
if s.flags.TigrisPrefetch {
592+
withHeader(req, "X-Tigris-Prefetch", "true")
593+
}
594+
595+
withHeader(req, "X-Tigris-List-Metadata", "true")
514596

515-
return &v2Objs, "", nil
597+
if s.flags.TigrisListContent {
598+
withHeader(req, "X-Tigris-List-Content", "true")
516599
}
600+
601+
req.Handlers.Unmarshal.Clear()
602+
req.Handlers.Unmarshal.PushFront(unmarshalListObjectsV2Response)
603+
604+
if err := req.Send(); err != nil {
605+
return nil, err
606+
}
607+
608+
reqId := s.getRequestId(req)
609+
610+
resp, ok := req.Data.(*ListObjectsV2Output)
611+
if !ok {
612+
return nil, fmt.Errorf("unexpected response type: %T", req.Data)
613+
}
614+
615+
return s.convertListOutputSpecial(resp, reqId)
517616
}
518617

519618
func metadataToLower(m map[string]*string) map[string]*string {
@@ -568,25 +667,48 @@ func (s *S3Backend) HeadBlob(param *HeadBlobInput) (*HeadBlobOutput, error) {
568667
}, nil
569668
}
570669

571-
func (s *S3Backend) ListBlobs(param *ListBlobsInput) (*ListBlobsOutput, error) {
572-
var maxKeys *int64
670+
func (s *S3Backend) convertListMetadataSpecial(in []MetadataEntry) map[string]*string {
671+
if len(in) == 0 {
672+
return nil
673+
}
573674

574-
if param.MaxKeys != nil {
575-
maxKeys = aws.Int64(int64(*param.MaxKeys))
675+
out := make(map[string]*string)
676+
for _, v := range in {
677+
out[v.Name] = &v.Value
576678
}
577679

578-
resp, reqId, err := s.ListObjectsV2(&s3.ListObjectsV2Input{
579-
Bucket: &s.bucket,
580-
Prefix: param.Prefix,
581-
Delimiter: param.Delimiter,
582-
MaxKeys: maxKeys,
583-
StartAfter: param.StartAfter,
584-
ContinuationToken: param.ContinuationToken,
585-
})
586-
if err != nil {
587-
return nil, err
680+
return out
681+
}
682+
683+
func (s *S3Backend) convertListOutputSpecial(resp *ListObjectsV2Output, reqId string) (*ListBlobsOutput, error) {
684+
prefixes := make([]BlobPrefixOutput, 0)
685+
items := make([]BlobItemOutput, 0)
686+
687+
for _, p := range resp.CommonPrefixes {
688+
prefixes = append(prefixes, BlobPrefixOutput{Prefix: p.Prefix})
588689
}
690+
for _, i := range resp.Contents {
691+
items = append(items, BlobItemOutput{
692+
Key: i.Key,
693+
ETag: i.ETag,
694+
LastModified: i.LastModified,
695+
Size: uint64(*i.Size),
696+
StorageClass: i.StorageClass,
697+
Metadata: s.convertListMetadataSpecial(i.Metadata),
698+
Content: i.Data,
699+
})
700+
}
701+
702+
return &ListBlobsOutput{
703+
Prefixes: prefixes,
704+
Items: items,
705+
NextContinuationToken: resp.NextContinuationToken,
706+
IsTruncated: *resp.IsTruncated,
707+
RequestId: reqId,
708+
}, nil
709+
}
589710

711+
func (s *S3Backend) convertListOutput(resp *s3.ListObjectsV2Output, reqId string) (*ListBlobsOutput, error) {
590712
prefixes := make([]BlobPrefixOutput, 0)
591713
items := make([]BlobItemOutput, 0)
592714

@@ -613,6 +735,34 @@ func (s *S3Backend) ListBlobs(param *ListBlobsInput) (*ListBlobsOutput, error) {
613735
}, nil
614736
}
615737

738+
func (s *S3Backend) ListBlobs(param *ListBlobsInput) (*ListBlobsOutput, error) {
739+
var maxKeys *int64
740+
741+
if param.MaxKeys != nil {
742+
maxKeys = aws.Int64(int64(*param.MaxKeys))
743+
}
744+
745+
req := &s3.ListObjectsV2Input{
746+
Bucket: &s.bucket,
747+
Prefix: param.Prefix,
748+
Delimiter: param.Delimiter,
749+
MaxKeys: maxKeys,
750+
StartAfter: param.StartAfter,
751+
ContinuationToken: param.ContinuationToken,
752+
}
753+
754+
if s.config.EnableSpecials {
755+
return s.listObjectsV2Special(req)
756+
}
757+
758+
resp, reqId, err := s.listObjects(req)
759+
if err != nil {
760+
return nil, err
761+
}
762+
763+
return s.convertListOutput(resp, reqId)
764+
}
765+
616766
func (s *S3Backend) DeleteBlob(param *DeleteBlobInput) (*DeleteBlobOutput, error) {
617767
req, _ := s.DeleteObjectRequest(&s3.DeleteObjectInput{
618768
Bucket: &s.bucket,

core/cfg/conf_s3.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ type S3Config struct {
6969
ACL string
7070
NoChecksum bool
7171
ListV2 bool
72-
ListV1Ext bool
72+
73+
EnableSpecials bool
7374

7475
Subdomain bool
7576

0 commit comments

Comments
 (0)