You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							205 lines
						
					
					
						
							5.7 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							205 lines
						
					
					
						
							5.7 KiB
						
					
					
				
								package s3_backend
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"fmt"
							 | 
						|
									"io"
							 | 
						|
									"os"
							 | 
						|
									"strings"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
								
							 | 
						|
									"github.com/aws/aws-sdk-go/service/s3"
							 | 
						|
									"github.com/aws/aws-sdk-go/service/s3/s3iface"
							 | 
						|
									"github.com/google/uuid"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage/backend"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								func init() {
							 | 
						|
									backend.BackendStorageFactories["s3"] = &S3BackendFactory{}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type S3BackendFactory struct {
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (factory *S3BackendFactory) StorageType() backend.StorageType {
							 | 
						|
									return backend.StorageType("s3")
							 | 
						|
								}
							 | 
						|
								func (factory *S3BackendFactory) BuildStorage(configuration backend.StringProperties, configPrefix string, id string) (backend.BackendStorage, error) {
							 | 
						|
									return newS3BackendStorage(configuration, configPrefix, id)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type S3BackendStorage struct {
							 | 
						|
									id                    string
							 | 
						|
									aws_access_key_id     string
							 | 
						|
									aws_secret_access_key string
							 | 
						|
									region                string
							 | 
						|
									bucket                string
							 | 
						|
									endpoint              string
							 | 
						|
									storageClass          string
							 | 
						|
									forcePathStyle        bool
							 | 
						|
									conn                  s3iface.S3API
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func newS3BackendStorage(configuration backend.StringProperties, configPrefix string, id string) (s *S3BackendStorage, err error) {
							 | 
						|
									s = &S3BackendStorage{}
							 | 
						|
									s.id = id
							 | 
						|
									s.aws_access_key_id = configuration.GetString(configPrefix + "aws_access_key_id")
							 | 
						|
									s.aws_secret_access_key = configuration.GetString(configPrefix + "aws_secret_access_key")
							 | 
						|
									s.region = configuration.GetString(configPrefix + "region")
							 | 
						|
									s.bucket = configuration.GetString(configPrefix + "bucket")
							 | 
						|
									s.endpoint = configuration.GetString(configPrefix + "endpoint")
							 | 
						|
									s.storageClass = configuration.GetString(configPrefix + "storage_class")
							 | 
						|
									s.forcePathStyle = util.ParseBool(configuration.GetString(configPrefix+"force_path_style"), true)
							 | 
						|
									if s.storageClass == "" {
							 | 
						|
										s.storageClass = "STANDARD_IA"
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									s.conn, err = createSession(s.aws_access_key_id, s.aws_secret_access_key, s.region, s.endpoint, s.forcePathStyle)
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("created backend storage s3.%s for region %s bucket %s", s.id, s.region, s.bucket)
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *S3BackendStorage) ToProperties() map[string]string {
							 | 
						|
									m := make(map[string]string)
							 | 
						|
									m["aws_access_key_id"] = s.aws_access_key_id
							 | 
						|
									m["aws_secret_access_key"] = s.aws_secret_access_key
							 | 
						|
									m["region"] = s.region
							 | 
						|
									m["bucket"] = s.bucket
							 | 
						|
									m["endpoint"] = s.endpoint
							 | 
						|
									m["storage_class"] = s.storageClass
							 | 
						|
									m["force_path_style"] = util.BoolToString(s.forcePathStyle)
							 | 
						|
									return m
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *S3BackendStorage) NewStorageFile(key string, tierInfo *volume_server_pb.VolumeInfo) backend.BackendStorageFile {
							 | 
						|
									if strings.HasPrefix(key, "/") {
							 | 
						|
										key = key[1:]
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									f := &S3BackendStorageFile{
							 | 
						|
										backendStorage: s,
							 | 
						|
										key:            key,
							 | 
						|
										tierInfo:       tierInfo,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return f
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *S3BackendStorage) CopyFile(f *os.File, fn func(progressed int64, percentage float32) error) (key string, size int64, err error) {
							 | 
						|
									randomUuid, _ := uuid.NewRandom()
							 | 
						|
									key = randomUuid.String()
							 | 
						|
								
							 | 
						|
									glog.V(1).Infof("copying dat file of %s to remote s3.%s as %s", f.Name(), s.id, key)
							 | 
						|
								
							 | 
						|
									util.Retry("upload to S3", func() error {
							 | 
						|
										size, err = uploadToS3(s.conn, f.Name(), s.bucket, key, s.storageClass, fn)
							 | 
						|
										return err
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *S3BackendStorage) DownloadFile(fileName string, key string, fn func(progressed int64, percentage float32) error) (size int64, err error) {
							 | 
						|
								
							 | 
						|
									glog.V(1).Infof("download dat file of %s from remote s3.%s as %s", fileName, s.id, key)
							 | 
						|
								
							 | 
						|
									size, err = downloadFromS3(s.conn, fileName, s.bucket, key, fn)
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s *S3BackendStorage) DeleteFile(key string) (err error) {
							 | 
						|
								
							 | 
						|
									glog.V(1).Infof("delete dat file %s from remote", key)
							 | 
						|
								
							 | 
						|
									err = deleteFromS3(s.conn, s.bucket, key)
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type S3BackendStorageFile struct {
							 | 
						|
									backendStorage *S3BackendStorage
							 | 
						|
									key            string
							 | 
						|
									tierInfo       *volume_server_pb.VolumeInfo
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) ReadAt(p []byte, off int64) (n int, err error) {
							 | 
						|
									datSize, _, _ := s3backendStorageFile.GetStat()
							 | 
						|
								
							 | 
						|
									if datSize > 0 && off >= datSize {
							 | 
						|
										return 0, io.EOF
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									bytesRange := fmt.Sprintf("bytes=%d-%d", off, off+int64(len(p))-1)
							 | 
						|
								
							 | 
						|
									getObjectOutput, getObjectErr := s3backendStorageFile.backendStorage.conn.GetObject(&s3.GetObjectInput{
							 | 
						|
										Bucket: &s3backendStorageFile.backendStorage.bucket,
							 | 
						|
										Key:    &s3backendStorageFile.key,
							 | 
						|
										Range:  &bytesRange,
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									if getObjectErr != nil {
							 | 
						|
										return 0, fmt.Errorf("bucket %s GetObject %s: %v", s3backendStorageFile.backendStorage.bucket, s3backendStorageFile.key, getObjectErr)
							 | 
						|
									}
							 | 
						|
									defer getObjectOutput.Body.Close()
							 | 
						|
								
							 | 
						|
									// glog.V(3).Infof("read %s %s", s3backendStorageFile.key, bytesRange)
							 | 
						|
									// glog.V(3).Infof("content range: %s, contentLength: %d", *getObjectOutput.ContentRange, *getObjectOutput.ContentLength)
							 | 
						|
								
							 | 
						|
									var readCount int
							 | 
						|
									for {
							 | 
						|
										p = p[readCount:]
							 | 
						|
										readCount, err = getObjectOutput.Body.Read(p)
							 | 
						|
										n += readCount
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if err == io.EOF {
							 | 
						|
										err = nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) WriteAt(p []byte, off int64) (n int, err error) {
							 | 
						|
									panic("not implemented")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) Truncate(off int64) error {
							 | 
						|
									panic("not implemented")
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) Close() error {
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) GetStat() (datSize int64, modTime time.Time, err error) {
							 | 
						|
								
							 | 
						|
									files := s3backendStorageFile.tierInfo.GetFiles()
							 | 
						|
								
							 | 
						|
									if len(files) == 0 {
							 | 
						|
										err = fmt.Errorf("remote file info not found")
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									datSize = int64(files[0].FileSize)
							 | 
						|
									modTime = time.Unix(int64(files[0].ModifiedTime), 0)
							 | 
						|
								
							 | 
						|
									return
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) Name() string {
							 | 
						|
									return s3backendStorageFile.key
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (s3backendStorageFile S3BackendStorageFile) Sync() error {
							 | 
						|
									return nil
							 | 
						|
								}
							 |