mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2025-09-23 04:03:35 +08:00
add s3 multipart upload
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"strings"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/satori/go.uuid"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer2"
|
||||
)
|
||||
|
||||
func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInput) (output *s3.CreateMultipartUploadOutput, code ErrorCode) {
|
||||
@@ -32,19 +39,76 @@ func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInp
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) completeMultipartUpload(input *s3.CompleteMultipartUploadInput) (output *s3.CompleteMultipartUploadOutput, code ErrorCode) {
|
||||
|
||||
uploadDirectory := s3a.genUploadsFolder(*input.Bucket) + "/" + *input.UploadId
|
||||
|
||||
entries, err := s3a.list(uploadDirectory, "", "", false, 0)
|
||||
if err != nil {
|
||||
glog.Errorf("completeMultipartUpload %s *s error: %v", *input.Bucket, *input.UploadId, err)
|
||||
return nil, ErrNoSuchUpload
|
||||
}
|
||||
|
||||
var finalParts []*filer_pb.FileChunk
|
||||
var offset int64
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name, ".part") && !entry.IsDirectory {
|
||||
for _, chunk := range entry.Chunks {
|
||||
finalParts = append(finalParts, &filer_pb.FileChunk{
|
||||
FileId: chunk.FileId,
|
||||
Offset: offset,
|
||||
Size: chunk.Size,
|
||||
Mtime: chunk.Mtime,
|
||||
ETag: chunk.ETag,
|
||||
})
|
||||
offset += int64(chunk.Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entryName := filepath.Base(*input.Key)
|
||||
dirName := filepath.Dir(*input.Key)
|
||||
if dirName == "." {
|
||||
dirName = ""
|
||||
}
|
||||
dirName = fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, *input.Bucket, dirName)
|
||||
|
||||
err = s3a.mkFile(dirName, entryName, finalParts)
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("completeMultipartUpload %s/%s error: %v", dirName, entryName, err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
output = &s3.CompleteMultipartUploadOutput{
|
||||
Bucket: input.Bucket,
|
||||
ETag: aws.String("\"" + filer2.ETag(finalParts) + "\""),
|
||||
Key: input.Key,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) abortMultipartUpload(input *s3.AbortMultipartUploadInput) (output *s3.AbortMultipartUploadOutput, code ErrorCode) {
|
||||
return
|
||||
|
||||
exists, err := s3a.exists(s3a.genUploadsFolder(*input.Bucket), *input.UploadId, true)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("bucket %s abort upload %s: %v", *input.Bucket, *input.UploadId, err)
|
||||
return nil, ErrNoSuchUpload
|
||||
}
|
||||
if exists {
|
||||
err = s3a.rm(s3a.genUploadsFolder(*input.Bucket), *input.UploadId, true, true, true)
|
||||
}
|
||||
if err != nil {
|
||||
glog.V(1).Infof("bucket %s remove upload %s: %v", *input.Bucket, *input.UploadId, err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
|
||||
return &s3.AbortMultipartUploadOutput{}, ErrNone
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput) (output *s3.ListMultipartUploadsOutput, code ErrorCode) {
|
||||
entries, err := s3a.list(s3a.genUploadsFolder(*input.Bucket))
|
||||
if err != nil {
|
||||
glog.Errorf("listMultipartUploads %s error: %v", *input.Bucket, err)
|
||||
return nil, ErrNoSuchUpload
|
||||
}
|
||||
|
||||
output = &s3.ListMultipartUploadsOutput{
|
||||
Bucket: input.Bucket,
|
||||
Delimiter: input.Delimiter,
|
||||
@@ -53,6 +117,13 @@ func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput
|
||||
MaxUploads: input.MaxUploads,
|
||||
Prefix: input.Prefix,
|
||||
}
|
||||
|
||||
entries, err := s3a.list(s3a.genUploadsFolder(*input.Bucket), *input.Prefix, *input.KeyMarker, true, int(*input.MaxUploads))
|
||||
if err != nil {
|
||||
glog.Errorf("listMultipartUploads %s error: %v", *input.Bucket, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.Extended != nil {
|
||||
key := entry.Extended["key"]
|
||||
@@ -66,5 +137,37 @@ func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) listObjectParts(input *s3.ListPartsInput) (output *s3.ListPartsOutput, code ErrorCode) {
|
||||
output = &s3.ListPartsOutput{
|
||||
Bucket: input.Bucket,
|
||||
Key: input.Key,
|
||||
UploadId: input.UploadId,
|
||||
MaxParts: input.MaxParts, // the maximum number of parts to return.
|
||||
PartNumberMarker: input.PartNumberMarker, // the part number starts after this, exclusive
|
||||
}
|
||||
|
||||
entries, err := s3a.list(s3a.genUploadsFolder(*input.Bucket)+"/"+*input.UploadId,
|
||||
"", fmt.Sprintf("%04d.part", *input.PartNumberMarker), false, int(*input.MaxParts))
|
||||
if err != nil {
|
||||
glog.Errorf("listObjectParts %s *s error: %v", *input.Bucket, *input.UploadId, err)
|
||||
return nil, ErrNoSuchUpload
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.HasSuffix(entry.Name, ".part") && !entry.IsDirectory {
|
||||
partNumberString := entry.Name[:len(entry.Name)-len(".part")]
|
||||
partNumber, err := strconv.Atoi(partNumberString)
|
||||
if err != nil {
|
||||
glog.Errorf("listObjectParts %s *s parse %s: %v", *input.Bucket, *input.UploadId, entry.Name, err)
|
||||
continue
|
||||
}
|
||||
output.Parts = append(output.Parts, &s3.Part{
|
||||
PartNumber: aws.Int64(int64(partNumber)),
|
||||
LastModified: aws.Time(time.Unix(entry.Attributes.Mtime, 0)),
|
||||
Size: aws.Int64(int64(filer2.TotalSize(entry.Chunks))),
|
||||
ETag: aws.String("\"" + filer2.ETag(entry.Chunks) + "\""),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ func (s3a *S3ApiServer) mkdir(parentDirectoryPath string, dirName string, fn fun
|
||||
Entry: entry,
|
||||
}
|
||||
|
||||
glog.V(1).Infof("create bucket: %v", request)
|
||||
glog.V(1).Infof("mkdir: %v", request)
|
||||
if _, err := client.CreateEntry(context.Background(), request); err != nil {
|
||||
return fmt.Errorf("mkdir %s/%s: %v", parentDirectoryPath, dirName, err)
|
||||
}
|
||||
@@ -43,12 +43,46 @@ func (s3a *S3ApiServer) mkdir(parentDirectoryPath string, dirName string, fn fun
|
||||
})
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) list(parentDirectoryPath string) (entries []*filer_pb.Entry, err error) {
|
||||
func (s3a *S3ApiServer) mkFile(parentDirectoryPath string, fileName string, chunks []*filer_pb.FileChunk) error {
|
||||
return s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
entry := &filer_pb.Entry{
|
||||
Name: fileName,
|
||||
IsDirectory: false,
|
||||
Attributes: &filer_pb.FuseAttributes{
|
||||
Mtime: time.Now().Unix(),
|
||||
Crtime: time.Now().Unix(),
|
||||
FileMode: uint32(0770),
|
||||
Uid: OS_UID,
|
||||
Gid: OS_GID,
|
||||
},
|
||||
Chunks: chunks,
|
||||
}
|
||||
|
||||
request := &filer_pb.CreateEntryRequest{
|
||||
Directory: parentDirectoryPath,
|
||||
Entry: entry,
|
||||
}
|
||||
|
||||
glog.V(1).Infof("create file: %s/%s", parentDirectoryPath, fileName)
|
||||
if _, err := client.CreateEntry(context.Background(), request); err != nil {
|
||||
return fmt.Errorf("create file %s/%s: %v", parentDirectoryPath, fileName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s3a *S3ApiServer) list(parentDirectoryPath, prefix, startFrom string, inclusive bool, limit int) (entries []*filer_pb.Entry, err error) {
|
||||
|
||||
err = s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {
|
||||
|
||||
request := &filer_pb.ListEntriesRequest{
|
||||
Directory: s3a.option.BucketsPath,
|
||||
Directory: s3a.option.BucketsPath,
|
||||
Prefix: prefix,
|
||||
StartFromFileName: startFrom,
|
||||
InclusiveStartFrom: inclusive,
|
||||
Limit: uint32(limit),
|
||||
}
|
||||
|
||||
glog.V(4).Infof("read directory: %v", request)
|
||||
@@ -101,10 +135,10 @@ func (s3a *S3ApiServer) exists(parentDirectoryPath string, entryName string, isD
|
||||
Name: entryName,
|
||||
}
|
||||
|
||||
glog.V(1).Infof("delete entry %v/%v: %v", parentDirectoryPath, entryName, request)
|
||||
glog.V(1).Infof("exists entry %v/%v: %v", parentDirectoryPath, entryName, request)
|
||||
resp, err := client.LookupDirectoryEntry(ctx, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete entry %s/%s: %v", parentDirectoryPath, entryName, err)
|
||||
return fmt.Errorf("exists entry %s/%s: %v", parentDirectoryPath, entryName, err)
|
||||
}
|
||||
|
||||
exists = resp.Entry.IsDirectory == isDirectory
|
||||
|
@@ -21,7 +21,7 @@ func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
var response ListAllMyBucketsResponse
|
||||
|
||||
entries, err := s3a.list(s3a.option.BucketsPath)
|
||||
entries, err := s3a.list(s3a.option.BucketsPath, "", "", false, 0)
|
||||
|
||||
if err != nil {
|
||||
writeErrorResponse(w, ErrInternalError, r.URL)
|
||||
|
@@ -143,6 +143,8 @@ func (s3a *S3ApiServer) ListMultipartUploadsHandler(w http.ResponseWriter, r *ht
|
||||
return
|
||||
}
|
||||
|
||||
// TODO handle encodingType
|
||||
|
||||
writeSuccessResponseXML(w, encodeResponse(response))
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,10 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer2"
|
||||
"github.com/chrislusf/seaweedfs/weed/glog"
|
||||
"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/chrislusf/seaweedfs/weed/filer2"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -126,7 +126,7 @@ func (s3a *S3ApiServer) listFilerEntries(bucket, originalPrefix string, maxKeys
|
||||
contents = append(contents, &s3.Object{
|
||||
Key: aws.String(fmt.Sprintf("%s%s", dir, entry.Name)),
|
||||
LastModified: aws.Time(time.Unix(entry.Attributes.Mtime, 0)),
|
||||
ETag: aws.String("\"2345sgfwetrewrt\""), // TODO add etag
|
||||
ETag: aws.String("\"" + filer2.ETag(entry.Chunks) + "\""),
|
||||
Size: aws.Int64(int64(filer2.TotalSize(entry.Chunks))),
|
||||
Owner: &s3.Owner{
|
||||
ID: aws.String("bcaf161ca5fb16fd081034f"),
|
||||
|
Reference in New Issue
Block a user