yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/apsara/bucket.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 apsara 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "net/http" 22 "strconv" 23 "time" 24 25 "github.com/aliyun/aliyun-oss-go-sdk/oss" 26 27 "yunion.io/x/jsonutils" 28 "yunion.io/x/log" 29 "yunion.io/x/pkg/errors" 30 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 ) 34 35 type SBucket struct { 36 multicloud.SBaseBucket 37 ApsaraTags 38 39 region *SRegion 40 41 Name string 42 Location string 43 CreationDate time.Time 44 StorageClass string 45 46 ExtranetEndpoint string 47 IntranetEndpoint string 48 DepartmentInfo 49 } 50 51 func (b *SBucket) GetGlobalId() string { 52 return b.Name 53 } 54 55 func (b *SBucket) GetName() string { 56 return b.Name 57 } 58 59 func (self *SBucket) GetOssClient() (*oss.Client, error) { 60 return self.region.GetOssClient() 61 } 62 63 func (b *SBucket) GetAcl() cloudprovider.TBucketACLType { 64 acl := b.region.GetBucketAcl(b.Name) 65 return cloudprovider.TBucketACLType(acl) 66 } 67 68 func (self *SRegion) GetBucketAcl(bucket string) string { 69 params := map[string]string{ 70 "AccountInfo": "aaa", 71 "x-acs-instanceid": bucket, 72 "Params": jsonutils.Marshal(map[string]string{"BucketName": bucket, "acl": "acl"}).String(), 73 } 74 resp, err := self.ossRequest("GetBucketAcl", params) 75 if err != nil { 76 return "" 77 } 78 acl, _ := resp.GetString("Data", "AccessControlPolicy", "AccessControlList", "Grant") 79 return acl 80 } 81 82 func (b *SBucket) GetLocation() string { 83 return b.Location 84 } 85 86 func (b *SBucket) GetIRegion() cloudprovider.ICloudRegion { 87 return b.region 88 } 89 90 func (b *SBucket) GetCreatedAt() time.Time { 91 return b.CreationDate 92 } 93 94 func (b *SBucket) GetStorageClass() string { 95 return b.StorageClass 96 } 97 98 func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { 99 return []cloudprovider.SBucketAccessUrl{ 100 { 101 Url: fmt.Sprintf("%s.%s", b.Name, b.ExtranetEndpoint), 102 Description: "ExtranetEndpoint", 103 Primary: true, 104 }, 105 } 106 } 107 108 func (self *SRegion) GetBucketSize(bucket string, department int) (int64, error) { 109 params := map[string]string{ 110 "Namespace": "acs_oss_dashboard", 111 "MetricName": "MeteringStorageUtilization", 112 "Period": "3600", 113 "Dimensions": jsonutils.Marshal([]map[string]string{ 114 {"BucketName": bucket}, 115 }).String(), 116 "Department": fmt.Sprintf("%d", department), 117 "StartTime": strconv.FormatInt(time.Now().Add(time.Hour*-24*2).Unix()*1000, 10), 118 "EndTime": strconv.FormatInt(time.Now().Unix()*1000, 10), 119 } 120 resp, err := self.client.metricsRequest("DescribeMetricList", params) 121 if err != nil { 122 return 0, nil 123 } 124 datapoints, err := resp.GetString("Datapoints") 125 if err != nil { 126 return 0, errors.Wrapf(err, "get datapoints") 127 } 128 obj, err := jsonutils.ParseString(datapoints) 129 if err != nil { 130 return 0, errors.Wrapf(err, "ParseString") 131 } 132 data := []struct { 133 Timestamp int64 134 Value int64 135 }{} 136 obj.Unmarshal(&data) 137 for i := range data { 138 return data[i].Value, nil 139 } 140 return 0, fmt.Errorf("no storage metric found") 141 } 142 143 func (b *SBucket) GetStats() cloudprovider.SBucketStats { 144 ret := cloudprovider.SBucketStats{ 145 SizeBytes: -1, 146 ObjectCount: -1, 147 } 148 dep, _ := strconv.Atoi(b.Department) 149 size, _ := b.region.GetBucketSize(b.Name, dep) 150 if size > 0 { 151 ret.SizeBytes = size 152 } 153 return ret 154 } 155 156 func (self *SRegion) GetBucketCapacity(bucket string, department int) (int64, error) { 157 params := map[string]string{ 158 "Params": jsonutils.Marshal(map[string]string{ 159 "BucketName": bucket, 160 "region": self.RegionId, 161 }).String(), 162 // 此参数必传,可以设任意值 163 "AccountInfo": "aaa", 164 "Department": fmt.Sprintf("%d", department), 165 } 166 resp, err := self.ossRequest("GetBucketStorageCapacity", params) 167 if err != nil { 168 return 0, errors.Wrapf(err, "GetBucketStorageCapacity") 169 } 170 return resp.Int("Data", "BucketUserQos", "StorageCapacity") 171 } 172 173 func (b *SBucket) GetLimit() cloudprovider.SBucketStats { 174 ret := cloudprovider.SBucketStats{ 175 SizeBytes: -1, 176 ObjectCount: -1, 177 } 178 dep, _ := strconv.Atoi(b.Department) 179 capa, _ := b.region.GetBucketCapacity(b.Name, dep) 180 if capa > 0 { 181 ret.SizeBytes = capa * 1024 * 1024 * 1024 182 } 183 return ret 184 } 185 186 func (b *SBucket) LimitSupport() cloudprovider.SBucketStats { 187 return b.GetLimit() 188 } 189 190 func (b *SBucket) SetAcl(aclStr cloudprovider.TBucketACLType) error { 191 osscli, err := b.GetOssClient() 192 if err != nil { 193 return errors.Wrap(err, "b.region.GetOssClient") 194 } 195 acl, err := str2Acl(string(aclStr)) 196 if err != nil { 197 return errors.Wrap(err, "str2Acl") 198 } 199 err = osscli.SetBucketACL(b.Name, acl) 200 if err != nil { 201 return errors.Wrap(err, "SetBucketACL") 202 } 203 return nil 204 } 205 206 func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) { 207 result := cloudprovider.SListObjectResult{} 208 osscli, err := b.GetOssClient() 209 if err != nil { 210 return result, errors.Wrap(err, "GetOssClient") 211 } 212 bucket, err := osscli.Bucket(b.Name) 213 if err != nil { 214 return result, errors.Wrap(err, "Bucket") 215 } 216 opts := make([]oss.Option, 0) 217 if len(prefix) > 0 { 218 opts = append(opts, oss.Prefix(prefix)) 219 } 220 if len(delimiter) > 0 { 221 opts = append(opts, oss.Delimiter(delimiter)) 222 } 223 if len(marker) > 0 { 224 opts = append(opts, oss.Marker(marker)) 225 } 226 if maxCount > 0 { 227 opts = append(opts, oss.MaxKeys(maxCount)) 228 } 229 oResult, err := bucket.ListObjects(opts...) 230 if err != nil { 231 return result, errors.Wrap(err, "ListObjects") 232 } 233 result.Objects = make([]cloudprovider.ICloudObject, 0) 234 for _, object := range oResult.Objects { 235 obj := &SObject{ 236 bucket: b, 237 SBaseCloudObject: cloudprovider.SBaseCloudObject{ 238 StorageClass: object.StorageClass, 239 Key: object.Key, 240 SizeBytes: object.Size, 241 ETag: object.ETag, 242 LastModified: object.LastModified, 243 }, 244 } 245 result.Objects = append(result.Objects, obj) 246 } 247 if oResult.CommonPrefixes != nil { 248 result.CommonPrefixes = make([]cloudprovider.ICloudObject, len(oResult.CommonPrefixes)) 249 for i, commPrefix := range oResult.CommonPrefixes { 250 result.CommonPrefixes[i] = &SObject{ 251 bucket: b, 252 SBaseCloudObject: cloudprovider.SBaseCloudObject{Key: commPrefix}, 253 } 254 } 255 } 256 result.IsTruncated = oResult.IsTruncated 257 result.NextMarker = oResult.NextMarker 258 return result, nil 259 } 260 261 func metaOpts(opts []oss.Option, meta http.Header) []oss.Option { 262 for k, v := range meta { 263 if len(v) == 0 { 264 continue 265 } 266 switch http.CanonicalHeaderKey(k) { 267 case cloudprovider.META_HEADER_CONTENT_TYPE: 268 opts = append(opts, oss.ContentType(v[0])) 269 case cloudprovider.META_HEADER_CONTENT_MD5: 270 opts = append(opts, oss.ContentMD5(v[0])) 271 case cloudprovider.META_HEADER_CONTENT_LANGUAGE: 272 opts = append(opts, oss.ContentLanguage(v[0])) 273 case cloudprovider.META_HEADER_CONTENT_ENCODING: 274 opts = append(opts, oss.ContentEncoding(v[0])) 275 case cloudprovider.META_HEADER_CONTENT_DISPOSITION: 276 opts = append(opts, oss.ContentDisposition(v[0])) 277 case cloudprovider.META_HEADER_CACHE_CONTROL: 278 opts = append(opts, oss.CacheControl(v[0])) 279 default: 280 opts = append(opts, oss.Meta(http.CanonicalHeaderKey(k), v[0])) 281 } 282 } 283 return opts 284 } 285 286 func (b *SBucket) PutObject(ctx context.Context, key string, input io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { 287 osscli, err := b.GetOssClient() 288 if err != nil { 289 return errors.Wrap(err, "GetOssClient") 290 } 291 bucket, err := osscli.Bucket(b.Name) 292 if err != nil { 293 return errors.Wrap(err, "Bucket") 294 } 295 opts := make([]oss.Option, 0) 296 if sizeBytes > 0 { 297 opts = append(opts, oss.ContentLength(sizeBytes)) 298 } 299 if meta != nil { 300 opts = metaOpts(opts, meta) 301 } 302 if len(cannedAcl) == 0 { 303 cannedAcl = b.GetAcl() 304 } 305 acl, err := str2Acl(string(cannedAcl)) 306 if err != nil { 307 return errors.Wrap(err, "") 308 } 309 opts = append(opts, oss.ObjectACL(acl)) 310 if len(storageClassStr) > 0 { 311 storageClass, err := str2StorageClass(storageClassStr) 312 if err != nil { 313 return errors.Wrap(err, "str2StorageClass") 314 } 315 opts = append(opts, oss.ObjectStorageClass(storageClass)) 316 } 317 return bucket.PutObject(key, input, opts...) 318 } 319 320 func (b *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) { 321 osscli, err := b.GetOssClient() 322 if err != nil { 323 return "", errors.Wrap(err, "GetOssClient") 324 } 325 bucket, err := osscli.Bucket(b.Name) 326 if err != nil { 327 return "", errors.Wrap(err, "Bucket") 328 } 329 opts := make([]oss.Option, 0) 330 if meta != nil { 331 opts = metaOpts(opts, meta) 332 } 333 if len(cannedAcl) == 0 { 334 cannedAcl = b.GetAcl() 335 } 336 acl, err := str2Acl(string(cannedAcl)) 337 if err != nil { 338 return "", errors.Wrap(err, "str2Acl") 339 } 340 opts = append(opts, oss.ObjectACL(acl)) 341 if len(storageClassStr) > 0 { 342 storageClass, err := str2StorageClass(storageClassStr) 343 if err != nil { 344 return "", errors.Wrap(err, "str2StorageClass") 345 } 346 opts = append(opts, oss.ObjectStorageClass(storageClass)) 347 } 348 result, err := bucket.InitiateMultipartUpload(key, opts...) 349 if err != nil { 350 return "", errors.Wrap(err, "bucket.InitiateMultipartUpload") 351 } 352 return result.UploadID, nil 353 } 354 355 func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, input io.Reader, partSize int64, offset, totalSize int64) (string, error) { 356 osscli, err := b.GetOssClient() 357 if err != nil { 358 return "", errors.Wrap(err, "GetOssClient") 359 } 360 bucket, err := osscli.Bucket(b.Name) 361 if err != nil { 362 return "", errors.Wrap(err, "Bucket") 363 } 364 imur := oss.InitiateMultipartUploadResult{ 365 Bucket: b.Name, 366 Key: key, 367 UploadID: uploadId, 368 } 369 part, err := bucket.UploadPart(imur, input, partSize, partIndex) 370 if err != nil { 371 return "", errors.Wrap(err, "bucket.UploadPart") 372 } 373 if b.region.client.debug { 374 log.Debugf("upload part key:%s uploadId:%s partIndex:%d etag:%s", key, uploadId, partIndex, part.ETag) 375 } 376 return part.ETag, nil 377 } 378 379 func (b *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error { 380 osscli, err := b.GetOssClient() 381 if err != nil { 382 return errors.Wrap(err, "GetOssClient") 383 } 384 bucket, err := osscli.Bucket(b.Name) 385 if err != nil { 386 return errors.Wrap(err, "Bucket") 387 } 388 imur := oss.InitiateMultipartUploadResult{ 389 Bucket: b.Name, 390 Key: key, 391 UploadID: uploadId, 392 } 393 parts := make([]oss.UploadPart, len(partEtags)) 394 for i := range partEtags { 395 parts[i] = oss.UploadPart{ 396 PartNumber: i + 1, 397 ETag: partEtags[i], 398 } 399 } 400 result, err := bucket.CompleteMultipartUpload(imur, parts) 401 if err != nil { 402 return errors.Wrap(err, "bucket.CompleteMultipartUpload") 403 } 404 if b.region.client.debug { 405 log.Debugf("CompleteMultipartUpload bucket:%s key:%s etag:%s location:%s", result.Bucket, result.Key, result.ETag, result.Location) 406 } 407 return nil 408 } 409 410 func (b *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error { 411 osscli, err := b.GetOssClient() 412 if err != nil { 413 return errors.Wrap(err, "GetOssClient") 414 } 415 bucket, err := osscli.Bucket(b.Name) 416 if err != nil { 417 return errors.Wrap(err, "Bucket") 418 } 419 imur := oss.InitiateMultipartUploadResult{ 420 Bucket: b.Name, 421 Key: key, 422 UploadID: uploadId, 423 } 424 err = bucket.AbortMultipartUpload(imur) 425 if err != nil { 426 return errors.Wrap(err, "AbortMultipartUpload") 427 } 428 return nil 429 } 430 431 func (b *SBucket) DeleteObject(ctx context.Context, key string) error { 432 osscli, err := b.GetOssClient() 433 if err != nil { 434 return errors.Wrap(err, "GetOssClient") 435 } 436 bucket, err := osscli.Bucket(b.Name) 437 if err != nil { 438 return errors.Wrap(err, "Bucket") 439 } 440 err = bucket.DeleteObject(key) 441 if err != nil { 442 return errors.Wrap(err, "DeleteObject") 443 } 444 return nil 445 } 446 447 func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) { 448 if method != "GET" && method != "PUT" && method != "DELETE" { 449 return "", errors.Error("unsupported method") 450 } 451 osscli, err := b.GetOssClient() 452 if err != nil { 453 return "", errors.Wrap(err, "GetOssClient") 454 } 455 bucket, err := osscli.Bucket(b.Name) 456 if err != nil { 457 return "", errors.Wrap(err, "Bucket") 458 } 459 urlStr, err := bucket.SignURL(key, oss.HTTPMethod(method), int64(expire/time.Second)) 460 if err != nil { 461 return "", errors.Wrap(err, "SignURL") 462 } 463 return urlStr, nil 464 } 465 466 func (b *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { 467 osscli, err := b.GetOssClient() 468 if err != nil { 469 return errors.Wrap(err, "GetOssClient") 470 } 471 bucket, err := osscli.Bucket(b.Name) 472 if err != nil { 473 return errors.Wrap(err, "Bucket") 474 } 475 opts := make([]oss.Option, 0) 476 if meta != nil { 477 opts = metaOpts(opts, meta) 478 } 479 if len(cannedAcl) == 0 { 480 cannedAcl = b.GetAcl() 481 } 482 acl, err := str2Acl(string(cannedAcl)) 483 if err != nil { 484 return errors.Wrap(err, "str2Acl") 485 } 486 opts = append(opts, oss.ObjectACL(acl)) 487 if len(storageClassStr) > 0 { 488 storageClass, err := str2StorageClass(storageClassStr) 489 if err != nil { 490 return errors.Wrap(err, "str2StorageClass") 491 } 492 opts = append(opts, oss.ObjectStorageClass(storageClass)) 493 } 494 _, err = bucket.CopyObjectFrom(srcBucket, srcKey, destKey, opts...) 495 if err != nil { 496 return errors.Wrap(err, "CopyObjectFrom") 497 } 498 return nil 499 } 500 501 func (b *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) { 502 osscli, err := b.GetOssClient() 503 if err != nil { 504 return nil, errors.Wrap(err, "GetOssClient") 505 } 506 bucket, err := osscli.Bucket(b.Name) 507 if err != nil { 508 return nil, errors.Wrap(err, "Bucket") 509 } 510 opts := make([]oss.Option, 0) 511 if rangeOpt != nil { 512 opts = append(opts, oss.NormalizedRange(rangeOpt.String())) 513 } 514 output, err := bucket.GetObject(key, opts...) 515 if err != nil { 516 return nil, errors.Wrap(err, "bucket.GetObject") 517 } 518 return output, nil 519 } 520 521 func (b *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partNumber int, srcBucket string, srcKey string, srcOffset int64, srcLength int64) (string, error) { 522 osscli, err := b.GetOssClient() 523 if err != nil { 524 return "", errors.Wrap(err, "GetOssClient") 525 } 526 bucket, err := osscli.Bucket(b.Name) 527 if err != nil { 528 return "", errors.Wrap(err, "Bucket") 529 } 530 imur := oss.InitiateMultipartUploadResult{ 531 Bucket: b.Name, 532 Key: key, 533 UploadID: uploadId, 534 } 535 opts := make([]oss.Option, 0) 536 part, err := bucket.UploadPartCopy(imur, srcBucket, srcKey, srcOffset, srcLength, partNumber, opts...) 537 if err != nil { 538 return "", errors.Wrap(err, "bucket.UploadPartCopy") 539 } 540 return part.ETag, nil 541 }