yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/cloudprovider/objectstore.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cloudprovider
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"regexp"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"yunion.io/x/jsonutils"
    29  	"yunion.io/x/log"
    30  	"yunion.io/x/pkg/errors"
    31  	"yunion.io/x/s3cli"
    32  
    33  	"yunion.io/x/onecloud/pkg/httperrors"
    34  )
    35  
    36  type TBucketACLType string
    37  
    38  const (
    39  	// 50 MB
    40  	MAX_PUT_OBJECT_SIZEBYTES = int64(1024 * 1024 * 50)
    41  
    42  	// ACLDefault = TBucketACLType("default")
    43  
    44  	ACLPrivate         = TBucketACLType(s3cli.CANNED_ACL_PRIVATE)
    45  	ACLAuthRead        = TBucketACLType(s3cli.CANNED_ACL_AUTH_READ)
    46  	ACLPublicRead      = TBucketACLType(s3cli.CANNED_ACL_PUBLIC_READ)
    47  	ACLPublicReadWrite = TBucketACLType(s3cli.CANNED_ACL_PUBLIC_READ_WRITE)
    48  	ACLUnknown         = TBucketACLType("")
    49  
    50  	META_HEADER_CACHE_CONTROL       = "Cache-Control"
    51  	META_HEADER_CONTENT_TYPE        = "Content-Type"
    52  	META_HEADER_CONTENT_DISPOSITION = "Content-Disposition"
    53  	META_HEADER_CONTENT_ENCODING    = "Content-Encoding"
    54  	META_HEADER_CONTENT_LANGUAGE    = "Content-Language"
    55  	META_HEADER_CONTENT_MD5         = "Content-MD5"
    56  
    57  	META_HEADER_PREFIX = "X-Yunion-Meta-"
    58  )
    59  
    60  type SBucketStats struct {
    61  	SizeBytes   int64
    62  	ObjectCount int
    63  }
    64  
    65  func (s SBucketStats) Equals(s2 SBucketStats) bool {
    66  	if s.SizeBytes == s2.SizeBytes && s.ObjectCount == s2.ObjectCount {
    67  		return true
    68  	} else {
    69  		return false
    70  	}
    71  }
    72  
    73  type SBucketAccessUrl struct {
    74  	Url         string
    75  	Description string
    76  	Primary     bool
    77  }
    78  
    79  type SBucketWebsiteRoutingRule struct {
    80  	ConditionErrorCode string
    81  	ConditionPrefix    string
    82  
    83  	RedirectProtocol         string
    84  	RedirectReplaceKey       string
    85  	RedirectReplaceKeyPrefix string
    86  }
    87  
    88  type SBucketWebsiteConf struct {
    89  	// 主页
    90  	Index string
    91  	// 错误时返回的文档
    92  	ErrorDocument string
    93  	// http或https
    94  	Protocol string
    95  
    96  	Rules []SBucketWebsiteRoutingRule
    97  	// 网站访问url,一般由bucketid,region等组成
    98  	Url string
    99  }
   100  
   101  type SBucketCORSRule struct {
   102  	AllowedMethods []string
   103  	// 允许的源站,可以设为*
   104  	AllowedOrigins []string
   105  	AllowedHeaders []string
   106  	MaxAgeSeconds  int
   107  	ExposeHeaders  []string
   108  	// 规则区别标识
   109  	Id string
   110  }
   111  
   112  type SBucketRefererConf struct {
   113  	// 域名列表
   114  	DomainList []string
   115  	// 域名列表
   116  	// enmu: Black-List, White-List
   117  	RefererType string
   118  	// 是否允许空referer 访问
   119  	AllowEmptyRefer bool
   120  
   121  	Enabled bool
   122  }
   123  
   124  type SBucketPolicyStatement struct {
   125  	// 授权的目标主体
   126  	Principal map[string][]string `json:"Principal,omitempty"`
   127  	// 授权的行为
   128  	Action []string `json:"Action,omitempty"`
   129  	// Allow|Deny
   130  	Effect string `json:"Effect,omitempty"`
   131  	// 被授权的资源
   132  	Resource []string `json:"Resource,omitempty"`
   133  	// 触发授权的条件
   134  	Condition map[string]map[string]interface{} `json:"Condition,omitempty"`
   135  
   136  	// 解析字段,主账号id:子账号id
   137  	PrincipalId []string
   138  	// map[主账号id:子账号id]子账号名称
   139  	PrincipalNames map[string]string
   140  	// Read|ReadWrite|FullControl
   141  	CannedAction string
   142  	// 资源路径
   143  	ResourcePath []string
   144  	// 根据index 生成
   145  	Id string
   146  }
   147  
   148  type SBucketPolicyStatementInput struct {
   149  	// 主账号id:子账号id
   150  	PrincipalId []string
   151  	// Read|ReadWrite|FullControl
   152  	CannedAction string
   153  	// Allow|Deny
   154  	Effect string
   155  	// 被授权的资源地址,/*
   156  	ResourcePath []string
   157  	// ip 条件
   158  	IpEquals    []string
   159  	IpNotEquals []string
   160  }
   161  
   162  type SBucketMultipartUploads struct {
   163  	// object name
   164  	ObjectName string
   165  	UploadID   string
   166  	// 发起人
   167  	Initiator string
   168  	// 发起时间
   169  	Initiated time.Time
   170  }
   171  
   172  type SBaseCloudObject struct {
   173  	Key          string
   174  	SizeBytes    int64
   175  	StorageClass string
   176  	ETag         string
   177  	LastModified time.Time
   178  	Meta         http.Header
   179  }
   180  
   181  type SListObjectResult struct {
   182  	Objects        []ICloudObject
   183  	NextMarker     string
   184  	CommonPrefixes []ICloudObject
   185  	IsTruncated    bool
   186  }
   187  
   188  type SGetObjectRange struct {
   189  	Start int64
   190  	End   int64
   191  }
   192  
   193  func (r SGetObjectRange) SizeBytes() int64 {
   194  	return r.End - r.Start + 1
   195  }
   196  
   197  var (
   198  	rangeExp = regexp.MustCompile(`(bytes=)?(\d*)-(\d*)`)
   199  )
   200  
   201  func ParseRange(rangeStr string) SGetObjectRange {
   202  	objRange := SGetObjectRange{}
   203  	if len(rangeStr) > 0 {
   204  		find := rangeExp.FindAllStringSubmatch(rangeStr, -1)
   205  		if len(find) > 0 && len(find[0]) > 3 {
   206  			objRange.Start, _ = strconv.ParseInt(find[0][2], 10, 64)
   207  			objRange.End, _ = strconv.ParseInt(find[0][3], 10, 64)
   208  		}
   209  	}
   210  	return objRange
   211  }
   212  
   213  func (r SGetObjectRange) String() string {
   214  	if r.Start > 0 && r.End > 0 {
   215  		return fmt.Sprintf("bytes=%d-%d", r.Start, r.End)
   216  	} else if r.Start > 0 && r.End <= 0 {
   217  		return fmt.Sprintf("bytes=%d-", r.Start)
   218  	} else if r.Start <= 0 && r.End > 0 {
   219  		return fmt.Sprintf("bytes=0-%d", r.End)
   220  	} else {
   221  		return ""
   222  	}
   223  }
   224  
   225  type ICloudBucket interface {
   226  	IVirtualResource
   227  
   228  	MaxPartCount() int
   229  	MaxPartSizeBytes() int64
   230  
   231  	//GetGlobalId() string
   232  	//GetName() string
   233  	GetAcl() TBucketACLType
   234  	GetLocation() string
   235  	GetIRegion() ICloudRegion
   236  	GetStorageClass() string
   237  	GetAccessUrls() []SBucketAccessUrl
   238  	GetStats() SBucketStats
   239  	GetLimit() SBucketStats
   240  	SetLimit(limit SBucketStats) error
   241  	LimitSupport() SBucketStats
   242  
   243  	SetAcl(acl TBucketACLType) error
   244  
   245  	ListObjects(prefix string, marker string, delimiter string, maxCount int) (SListObjectResult, error)
   246  
   247  	CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) error
   248  	GetObject(ctx context.Context, key string, rangeOpt *SGetObjectRange) (io.ReadCloser, error)
   249  
   250  	DeleteObject(ctx context.Context, keys string) error
   251  	GetTempUrl(method string, key string, expire time.Duration) (string, error)
   252  
   253  	PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) error
   254  
   255  	NewMultipartUpload(ctx context.Context, key string, cannedAcl TBucketACLType, storageClassStr string, meta http.Header) (string, error)
   256  	UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error)
   257  	CopyPart(ctx context.Context, key string, uploadId string, partIndex int, srcBucketName string, srcKey string, srcOffset int64, srcLength int64) (string, error)
   258  	CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error
   259  	AbortMultipartUpload(ctx context.Context, key string, uploadId string) error
   260  
   261  	SetWebsite(conf SBucketWebsiteConf) error
   262  	GetWebsiteConf() (SBucketWebsiteConf, error)
   263  	DeleteWebSiteConf() error
   264  
   265  	SetCORS(rules []SBucketCORSRule) error
   266  	GetCORSRules() ([]SBucketCORSRule, error)
   267  	DeleteCORS() error
   268  
   269  	SetReferer(conf SBucketRefererConf) error
   270  	GetReferer() (SBucketRefererConf, error)
   271  
   272  	GetCdnDomains() ([]SCdnDomain, error)
   273  
   274  	GetPolicy() ([]SBucketPolicyStatement, error)
   275  	SetPolicy(policy SBucketPolicyStatementInput) error
   276  	DeletePolicy(id []string) ([]SBucketPolicyStatement, error)
   277  
   278  	ListMultipartUploads() ([]SBucketMultipartUploads, error)
   279  }
   280  
   281  type ICloudObject interface {
   282  	GetIBucket() ICloudBucket
   283  
   284  	GetKey() string
   285  	GetSizeBytes() int64
   286  	GetLastModified() time.Time
   287  	GetStorageClass() string
   288  	GetETag() string
   289  
   290  	GetMeta() http.Header
   291  	SetMeta(ctx context.Context, meta http.Header) error
   292  
   293  	GetAcl() TBucketACLType
   294  	SetAcl(acl TBucketACLType) error
   295  }
   296  
   297  type SCloudObject struct {
   298  	Key          string
   299  	SizeBytes    int64
   300  	StorageClass string
   301  	ETag         string
   302  	LastModified time.Time
   303  	Meta         http.Header
   304  	Acl          string
   305  }
   306  
   307  func ICloudObject2Struct(obj ICloudObject) SCloudObject {
   308  	return SCloudObject{
   309  		Key:          obj.GetKey(),
   310  		SizeBytes:    obj.GetSizeBytes(),
   311  		StorageClass: obj.GetStorageClass(),
   312  		ETag:         obj.GetETag(),
   313  		LastModified: obj.GetLastModified(),
   314  		Meta:         obj.GetMeta(),
   315  		Acl:          string(obj.GetAcl()),
   316  	}
   317  }
   318  
   319  func ICloudObject2JSONObject(obj ICloudObject) jsonutils.JSONObject {
   320  	return jsonutils.Marshal(ICloudObject2Struct(obj))
   321  }
   322  
   323  func (o *SBaseCloudObject) GetKey() string {
   324  	return o.Key
   325  }
   326  
   327  func (o *SBaseCloudObject) GetSizeBytes() int64 {
   328  	return o.SizeBytes
   329  }
   330  
   331  func (o *SBaseCloudObject) GetLastModified() time.Time {
   332  	return o.LastModified
   333  }
   334  
   335  func (o *SBaseCloudObject) GetStorageClass() string {
   336  	return o.StorageClass
   337  }
   338  
   339  func (o *SBaseCloudObject) GetETag() string {
   340  	return o.ETag
   341  }
   342  
   343  func (o *SBaseCloudObject) GetMeta() http.Header {
   344  	return o.Meta
   345  }
   346  
   347  //func (o *SBaseCloudObject) SetMeta(meta http.Header) error {
   348  //    return nil
   349  //}
   350  
   351  func GetIBucketById(region ICloudRegion, name string) (ICloudBucket, error) {
   352  	buckets, err := region.GetIBuckets()
   353  	if err != nil {
   354  		return nil, errors.Wrap(err, "region.GetIBuckets")
   355  	}
   356  	for i := range buckets {
   357  		if buckets[i].GetGlobalId() == name {
   358  			return buckets[i], nil
   359  		}
   360  	}
   361  	return nil, ErrNotFound
   362  }
   363  
   364  func GetIBucketByName(region ICloudRegion, name string) (ICloudBucket, error) {
   365  	buckets, err := region.GetIBuckets()
   366  	if err != nil {
   367  		return nil, errors.Wrap(err, "region.GetIBuckets")
   368  	}
   369  	for i := range buckets {
   370  		if buckets[i].GetName() == name {
   371  			return buckets[i], nil
   372  		}
   373  	}
   374  	return nil, ErrNotFound
   375  }
   376  
   377  func GetIBucketStats(bucket ICloudBucket) (SBucketStats, error) {
   378  	stats := SBucketStats{
   379  		ObjectCount: -1,
   380  		SizeBytes:   -1,
   381  	}
   382  	objs, err := bucket.ListObjects("", "", "", 1000)
   383  	if err != nil {
   384  		return stats, errors.Wrap(err, "GetIObjects")
   385  	}
   386  	if objs.IsTruncated {
   387  		return stats, errors.Wrap(httperrors.ErrTooLarge, "too many objects")
   388  	}
   389  	stats.ObjectCount, stats.SizeBytes = 0, 0
   390  	for _, obj := range objs.Objects {
   391  		stats.SizeBytes += obj.GetSizeBytes()
   392  		stats.ObjectCount += 1
   393  	}
   394  	return stats, nil
   395  }
   396  
   397  type cloudObjectList []ICloudObject
   398  
   399  func (a cloudObjectList) Len() int           { return len(a) }
   400  func (a cloudObjectList) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
   401  func (a cloudObjectList) Less(i, j int) bool { return a[i].GetKey() < a[j].GetKey() }
   402  
   403  func GetPagedObjects(bucket ICloudBucket, objectPrefix string, isRecursive bool, marker string, maxCount int) ([]ICloudObject, string, error) {
   404  	delimiter := "/"
   405  	if isRecursive {
   406  		delimiter = ""
   407  	}
   408  	if maxCount > 1000 || maxCount <= 0 {
   409  		maxCount = 1000
   410  	}
   411  	ret := make([]ICloudObject, 0)
   412  	result, err := bucket.ListObjects(objectPrefix, marker, delimiter, maxCount)
   413  	if err != nil {
   414  		return nil, "", errors.Wrap(err, "bucket.ListObjects")
   415  	}
   416  	// Send all objects
   417  	for i := range result.Objects {
   418  		// if delimited, skip the first object ends with delimiter
   419  		if !isRecursive && result.Objects[i].GetKey() == objectPrefix && strings.HasSuffix(objectPrefix, delimiter) {
   420  			continue
   421  		}
   422  		ret = append(ret, result.Objects[i])
   423  		marker = result.Objects[i].GetKey()
   424  	}
   425  	// Send all common prefixes if any.
   426  	// NOTE: prefixes are only present if the request is delimited.
   427  	if len(result.CommonPrefixes) > 0 {
   428  		ret = append(ret, result.CommonPrefixes...)
   429  	}
   430  	// sort prefix by name in ascending order
   431  	sort.Sort(cloudObjectList(ret))
   432  	// If next marker present, save it for next request.
   433  	if result.NextMarker != "" {
   434  		marker = result.NextMarker
   435  	}
   436  	// If not truncated, no more objects
   437  	if !result.IsTruncated {
   438  		marker = ""
   439  	}
   440  	return ret, marker, nil
   441  }
   442  
   443  func GetAllObjects(bucket ICloudBucket, objectPrefix string, isRecursive bool) ([]ICloudObject, error) {
   444  	ret := make([]ICloudObject, 0)
   445  	// Save marker for next request.
   446  	var marker string
   447  	for {
   448  		// Get list of objects a maximum of 1000 per request.
   449  		result, marker, err := GetPagedObjects(bucket, objectPrefix, isRecursive, marker, 1000)
   450  		if err != nil {
   451  			return nil, errors.Wrap(err, "bucket.ListObjects")
   452  		}
   453  		ret = append(ret, result...)
   454  		if marker == "" {
   455  			break
   456  		}
   457  	}
   458  	return ret, nil
   459  }
   460  
   461  func GetIObject(bucket ICloudBucket, objectPrefix string) (ICloudObject, error) {
   462  	tryPrefix := []string{objectPrefix}
   463  	if strings.HasSuffix(objectPrefix, "/") {
   464  		tryPrefix = append(tryPrefix, objectPrefix[:len(objectPrefix)-1])
   465  	}
   466  	for _, pref := range tryPrefix {
   467  		result, err := bucket.ListObjects(pref, "", "", 1)
   468  		if err != nil {
   469  			return nil, errors.Wrap(err, "bucket.ListObjects")
   470  		}
   471  		objects := result.Objects
   472  		if len(objects) > 0 && objects[0].GetKey() == objectPrefix {
   473  			return objects[0], nil
   474  		}
   475  	}
   476  	return nil, ErrNotFound
   477  }
   478  
   479  func Makedir(ctx context.Context, bucket ICloudBucket, key string) error {
   480  	segs := make([]string, 0)
   481  	for _, seg := range strings.Split(key, "/") {
   482  		if len(seg) > 0 {
   483  			segs = append(segs, seg)
   484  		}
   485  	}
   486  	path := strings.Join(segs, "/") + "/"
   487  	err := bucket.PutObject(ctx, path, strings.NewReader(""), 0, bucket.GetAcl(), "", nil)
   488  	if err != nil {
   489  		return errors.Wrap(err, "PutObject")
   490  	}
   491  	return nil
   492  }
   493  
   494  func UploadObject(ctx context.Context, bucket ICloudBucket, key string, blocksz int64, input io.Reader, sizeBytes int64, cannedAcl TBucketACLType, storageClass string, meta http.Header, debug bool) error {
   495  	if blocksz <= 0 {
   496  		blocksz = MAX_PUT_OBJECT_SIZEBYTES
   497  	}
   498  	if sizeBytes < blocksz {
   499  		if debug {
   500  			log.Debugf("too small, put object in one shot")
   501  		}
   502  		return bucket.PutObject(ctx, key, input, sizeBytes, cannedAcl, storageClass, meta)
   503  	}
   504  	partSize := blocksz
   505  	partCount := sizeBytes / partSize
   506  	if partCount*partSize < sizeBytes {
   507  		partCount += 1
   508  	}
   509  	if partCount > int64(bucket.MaxPartCount()) {
   510  		partCount = int64(bucket.MaxPartCount())
   511  		partSize = sizeBytes / partCount
   512  		if partSize*partCount < sizeBytes {
   513  			partSize += 1
   514  		}
   515  		if partSize > bucket.MaxPartSizeBytes() {
   516  			return errors.Error("too larget object")
   517  		}
   518  	}
   519  	if debug {
   520  		log.Debugf("multipart upload part count %d part size %d", partCount, partSize)
   521  	}
   522  	uploadId, err := bucket.NewMultipartUpload(ctx, key, cannedAcl, storageClass, meta)
   523  	if err != nil {
   524  		return errors.Wrap(err, "bucket.NewMultipartUpload")
   525  	}
   526  	etags := make([]string, partCount)
   527  	offset := int64(0)
   528  	for i := 0; i < int(partCount); i += 1 {
   529  		if i == int(partCount)-1 {
   530  			partSize = sizeBytes - partSize*(partCount-1)
   531  		}
   532  		if debug {
   533  			log.Debugf("UploadPart %d %d", i+1, partSize)
   534  		}
   535  		etag, err := bucket.UploadPart(ctx, key, uploadId, i+1, io.LimitReader(input, partSize), partSize, offset, sizeBytes)
   536  		if err != nil {
   537  			err2 := bucket.AbortMultipartUpload(ctx, key, uploadId)
   538  			if err2 != nil {
   539  				log.Errorf("bucket.AbortMultipartUpload error %s", err2)
   540  			}
   541  			return errors.Wrap(err, "bucket.UploadPart")
   542  		}
   543  		offset += partSize
   544  		etags[i] = etag
   545  	}
   546  	err = bucket.CompleteMultipartUpload(ctx, key, uploadId, etags)
   547  	if err != nil {
   548  		err2 := bucket.AbortMultipartUpload(ctx, key, uploadId)
   549  		if err2 != nil {
   550  			log.Errorf("bucket.AbortMultipartUpload error %s", err2)
   551  		}
   552  		return errors.Wrap(err, "CompleteMultipartUpload")
   553  	}
   554  	return nil
   555  }
   556  
   557  func DeletePrefix(ctx context.Context, bucket ICloudBucket, prefix string) error {
   558  	objs, err := GetAllObjects(bucket, prefix, true)
   559  	if err != nil {
   560  		return errors.Wrap(err, "bucket.GetIObjects")
   561  	}
   562  	for i := range objs {
   563  		err := bucket.DeleteObject(ctx, objs[i].GetKey())
   564  		if err != nil {
   565  			return errors.Wrap(err, "bucket.DeleteObject")
   566  		}
   567  	}
   568  	return nil
   569  }
   570  
   571  func MergeMeta(src http.Header, dst http.Header) http.Header {
   572  	if src != nil && dst != nil {
   573  		ret := http.Header{}
   574  		for k, vs := range src {
   575  			for _, v := range vs {
   576  				ret.Add(k, v)
   577  			}
   578  		}
   579  		for k, vs := range dst {
   580  			for _, v := range vs {
   581  				ret.Add(k, v)
   582  			}
   583  		}
   584  		return ret
   585  	} else if src != nil && dst == nil {
   586  		return src
   587  	} else if src == nil && dst != nil {
   588  		return dst
   589  	} else {
   590  		return nil
   591  	}
   592  }
   593  
   594  func CopyObject(ctx context.Context, blocksz int64, dstBucket ICloudBucket, dstKey string, srcBucket ICloudBucket, srcKey string, dstMeta http.Header, debug bool) error {
   595  
   596  	srcObj, err := GetIObject(srcBucket, srcKey)
   597  	if err != nil {
   598  		return errors.Wrap(err, "GetIObject")
   599  	}
   600  	if blocksz <= 0 {
   601  		blocksz = MAX_PUT_OBJECT_SIZEBYTES
   602  	}
   603  	sizeBytes := srcObj.GetSizeBytes()
   604  	if sizeBytes < blocksz {
   605  		if debug {
   606  			log.Debugf("too small, copy object in one shot")
   607  		}
   608  		srcStream, err := srcBucket.GetObject(ctx, srcKey, nil)
   609  		if err != nil {
   610  			return errors.Wrap(err, "srcBucket.GetObject")
   611  		}
   612  		defer srcStream.Close()
   613  		err = dstBucket.PutObject(ctx, dstKey, srcStream, sizeBytes, srcObj.GetAcl(), srcObj.GetStorageClass(), MergeMeta(srcObj.GetMeta(), dstMeta))
   614  		if err != nil {
   615  			return errors.Wrap(err, "dstBucket.PutObject")
   616  		}
   617  		return nil
   618  	}
   619  	partSize := blocksz
   620  	partCount := sizeBytes / partSize
   621  	if partCount*partSize < sizeBytes {
   622  		partCount += 1
   623  	}
   624  	if partCount > int64(dstBucket.MaxPartCount()) {
   625  		partCount = int64(dstBucket.MaxPartCount())
   626  		partSize = sizeBytes / partCount
   627  		if partSize*partCount < sizeBytes {
   628  			partSize += 1
   629  		}
   630  		if partSize > dstBucket.MaxPartSizeBytes() {
   631  			return errors.Error("too larget object")
   632  		}
   633  	}
   634  	if debug {
   635  		log.Debugf("multipart upload part count %d part size %d", partCount, partSize)
   636  	}
   637  	uploadId, err := dstBucket.NewMultipartUpload(ctx, dstKey, srcObj.GetAcl(), srcObj.GetStorageClass(), MergeMeta(srcObj.GetMeta(), dstMeta))
   638  	if err != nil {
   639  		return errors.Wrap(err, "bucket.NewMultipartUpload")
   640  	}
   641  	etags := make([]string, partCount)
   642  	offset := int64(0)
   643  	for i := 0; i < int(partCount); i += 1 {
   644  		start := int64(i) * partSize
   645  		if i == int(partCount)-1 {
   646  			partSize = sizeBytes - partSize*(partCount-1)
   647  		}
   648  		end := start + partSize - 1
   649  		rangeOpt := SGetObjectRange{
   650  			Start: start,
   651  			End:   end,
   652  		}
   653  		if debug {
   654  			log.Debugf("UploadPart %d %d range: %s (%d)", i+1, partSize, rangeOpt.String(), rangeOpt.SizeBytes())
   655  		}
   656  		srcStream, err := srcBucket.GetObject(ctx, srcKey, &rangeOpt)
   657  		if err == nil {
   658  			defer srcStream.Close()
   659  			var etag string
   660  			etag, err = dstBucket.UploadPart(ctx, dstKey, uploadId, i+1, io.LimitReader(srcStream, partSize), partSize, offset, sizeBytes)
   661  			if err == nil {
   662  				etags[i] = etag
   663  				continue
   664  			}
   665  		}
   666  		offset += partSize
   667  		if err != nil {
   668  			err2 := dstBucket.AbortMultipartUpload(ctx, dstKey, uploadId)
   669  			if err2 != nil {
   670  				log.Errorf("bucket.AbortMultipartUpload error %s", err2)
   671  			}
   672  			return errors.Wrap(err, "bucket.UploadPart")
   673  		}
   674  	}
   675  	err = dstBucket.CompleteMultipartUpload(ctx, dstKey, uploadId, etags)
   676  	if err != nil {
   677  		err2 := dstBucket.AbortMultipartUpload(ctx, dstKey, uploadId)
   678  		if err2 != nil {
   679  			log.Errorf("bucket.AbortMultipartUpload error %s", err2)
   680  		}
   681  		return errors.Wrap(err, "CompleteMultipartUpload")
   682  	}
   683  	return nil
   684  }
   685  
   686  func CopyPart(ctx context.Context,
   687  	iDstBucket ICloudBucket, dstKey string, uploadId string, partNumber int,
   688  	iSrcBucket ICloudBucket, srcKey string, rangeOpt *SGetObjectRange,
   689  ) (string, error) {
   690  	srcReader, err := iSrcBucket.GetObject(ctx, srcKey, rangeOpt)
   691  	if err != nil {
   692  		return "", errors.Wrap(err, "iSrcBucket.GetObject")
   693  	}
   694  	defer srcReader.Close()
   695  
   696  	etag, err := iDstBucket.UploadPart(ctx, dstKey, uploadId, partNumber, io.LimitReader(srcReader, rangeOpt.SizeBytes()), rangeOpt.SizeBytes(), 0, 0)
   697  	if err != nil {
   698  		return "", errors.Wrap(err, "iDstBucket.UploadPart")
   699  	}
   700  	return etag, nil
   701  }
   702  
   703  func ObjectSetMeta(ctx context.Context,
   704  	bucket ICloudBucket, obj ICloudObject,
   705  	meta http.Header,
   706  ) error {
   707  	return bucket.CopyObject(ctx, obj.GetKey(), bucket.GetName(), obj.GetKey(), obj.GetAcl(), obj.GetStorageClass(), meta)
   708  }
   709  
   710  func MetaToHttpHeader(metaPrefix string, meta http.Header) http.Header {
   711  	hdr := http.Header{}
   712  	for k, v := range meta {
   713  		if len(v) == 0 || len(v[0]) == 0 {
   714  			continue
   715  		}
   716  		k = http.CanonicalHeaderKey(k)
   717  		switch k {
   718  		case META_HEADER_CACHE_CONTROL,
   719  			META_HEADER_CONTENT_TYPE,
   720  			META_HEADER_CONTENT_DISPOSITION,
   721  			META_HEADER_CONTENT_ENCODING,
   722  			META_HEADER_CONTENT_LANGUAGE,
   723  			META_HEADER_CONTENT_MD5:
   724  			hdr.Set(k, v[0])
   725  		default:
   726  			hdr.Set(fmt.Sprintf("%s%s", metaPrefix, k), v[0])
   727  		}
   728  	}
   729  	return hdr
   730  }
   731  
   732  func FetchMetaFromHttpHeader(metaPrefix string, headers http.Header) http.Header {
   733  	metaPrefix = http.CanonicalHeaderKey(metaPrefix)
   734  	meta := http.Header{}
   735  	for hdr, vals := range headers {
   736  		hdr = http.CanonicalHeaderKey(hdr)
   737  		if strings.HasPrefix(hdr, metaPrefix) {
   738  			for _, val := range vals {
   739  				meta.Add(hdr[len(metaPrefix):], val)
   740  			}
   741  		}
   742  	}
   743  	for _, hdr := range []string{
   744  		META_HEADER_CONTENT_TYPE,
   745  		META_HEADER_CONTENT_ENCODING,
   746  		META_HEADER_CONTENT_DISPOSITION,
   747  		META_HEADER_CONTENT_LANGUAGE,
   748  		META_HEADER_CACHE_CONTROL,
   749  	} {
   750  		val := headers.Get(hdr)
   751  		if len(val) > 0 {
   752  			meta.Set(hdr, val)
   753  		}
   754  	}
   755  	return meta
   756  }
   757  
   758  func SetBucketCORS(ibucket ICloudBucket, rules []SBucketCORSRule) error {
   759  	if len(rules) == 0 {
   760  		return nil
   761  	}
   762  
   763  	oldRules, err := ibucket.GetCORSRules()
   764  	if err != nil {
   765  		return errors.Wrap(err, "ibucket.GetCORSRules()")
   766  	}
   767  
   768  	newSet := []SBucketCORSRule{}
   769  	updateSet := map[int]SBucketCORSRule{}
   770  	for i := range rules {
   771  		index, err := strconv.Atoi(rules[i].Id)
   772  		if err == nil && index < len(oldRules) {
   773  			updateSet[index] = rules[i]
   774  		} else {
   775  			newSet = append(newSet, rules[i])
   776  		}
   777  	}
   778  
   779  	updatedRules := []SBucketCORSRule{}
   780  	for i := range oldRules {
   781  		if _, ok := updateSet[i]; !ok {
   782  			updatedRules = append(updatedRules, oldRules[i])
   783  		} else {
   784  			updatedRules = append(updatedRules, updateSet[i])
   785  		}
   786  	}
   787  	updatedRules = append(updatedRules, newSet...)
   788  
   789  	err = ibucket.SetCORS(updatedRules)
   790  	if err != nil {
   791  		return errors.Wrap(err, "ibucket.SetCORS(updatedRules)")
   792  	}
   793  	return nil
   794  }
   795  
   796  func DeleteBucketCORS(ibucket ICloudBucket, id []string) ([]SBucketCORSRule, error) {
   797  	if len(id) == 0 {
   798  		return nil, nil
   799  	}
   800  	deletedRules := []SBucketCORSRule{}
   801  
   802  	oldRules, err := ibucket.GetCORSRules()
   803  	if err != nil {
   804  		return nil, errors.Wrap(err, "ibucket.GetCORSRules()")
   805  	}
   806  
   807  	excludeMap := map[int]bool{}
   808  	for i := range id {
   809  		index, err := strconv.Atoi(id[i])
   810  		if err == nil && index < len(oldRules) {
   811  			excludeMap[index] = true
   812  		}
   813  	}
   814  	if len(excludeMap) == 0 {
   815  		return nil, nil
   816  	}
   817  
   818  	newRules := []SBucketCORSRule{}
   819  	for i := range oldRules {
   820  		if _, ok := excludeMap[i]; !ok {
   821  			newRules = append(newRules, oldRules[i])
   822  		} else {
   823  			deletedRules = append(deletedRules, oldRules[i])
   824  		}
   825  	}
   826  
   827  	if len(newRules) == 0 {
   828  		err = ibucket.DeleteCORS()
   829  		if err != nil {
   830  			return nil, errors.Wrapf(err, "ibucket.DeleteCORS()")
   831  		}
   832  	} else {
   833  		err = ibucket.SetCORS(newRules)
   834  		if err != nil {
   835  			return nil, errors.Wrapf(err, "ibucket.SetBucketCORS(newRules)")
   836  		}
   837  	}
   838  
   839  	return deletedRules, nil
   840  }
   841  
   842  func SetBucketTags(ctx context.Context, iBucket ICloudBucket, mangerId string, tags map[string]string) (TagsUpdateInfo, error) {
   843  	ret := TagsUpdateInfo{}
   844  	old, err := iBucket.GetTags()
   845  	if err != nil {
   846  		if errors.Cause(err) == ErrNotImplemented || errors.Cause(err) == ErrNotSupported {
   847  			return ret, nil
   848  		}
   849  		return ret, errors.Wrapf(err, "iBucket.GetTags")
   850  	}
   851  	ret.OldTags, ret.NewTags = old, tags
   852  	if !ret.IsChanged() {
   853  		return ret, nil
   854  	}
   855  	return ret, SetTags(ctx, iBucket, mangerId, tags, true)
   856  }