yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/disk.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 aws 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strings" 22 "time" 23 24 "github.com/aws/aws-sdk-go/service/ec2" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/errors" 29 30 "yunion.io/x/cloudmux/pkg/apis/billing" 31 api "yunion.io/x/cloudmux/pkg/apis/compute" 32 "yunion.io/x/cloudmux/pkg/cloudprovider" 33 "yunion.io/x/cloudmux/pkg/multicloud" 34 ) 35 36 type SMountInstances struct { 37 MountInstance []string 38 } 39 40 type SDisk struct { 41 storage *SStorage 42 multicloud.SDisk 43 AwsTags 44 45 RegionId string 46 ZoneId string // AvailabilityZone 47 DiskId string // VolumeId 48 49 DiskName string // Tag Name 50 Size int // Size GB 51 Category string // VolumeType 52 Type string // system | data 53 Status string // State 54 AttachmentStatus string // attachment.status 55 Device string // Device 56 InstanceId string // InstanceId 57 Encrypted bool // Encrypted 58 SourceSnapshotId string // SnapshotId 59 Iops int // Iops 60 61 CreationTime time.Time // CreateTime 62 AttachedTime time.Time // AttachTime 63 DetachedTime time.Time 64 65 DeleteWithInstance bool // DeleteOnTermination 66 EnableAutoSnapshot bool 67 EnableAutomatedSnapshotPolicy bool 68 69 /*下面这些字段也许不需要*/ 70 AutoSnapshotPolicyId string 71 DeleteAutoSnapshot bool 72 Description string 73 DiskChargeType InstanceChargeType 74 ExpiredTime time.Time 75 ImageId string 76 MountInstances SMountInstances 77 Portable bool 78 ProductCode string 79 ResourceGroupId string 80 } 81 82 func (self *SDisk) GetId() string { 83 return self.DiskId 84 } 85 86 func (self *SDisk) GetName() string { 87 if len(self.DiskName) > 0 { 88 return self.DiskName 89 } 90 return self.DiskId 91 } 92 93 func (self *SDisk) GetIops() int { 94 return self.Iops 95 } 96 97 func (self *SDisk) GetGlobalId() string { 98 return self.DiskId 99 } 100 101 func (self *SDisk) GetStatus() string { 102 // creating | available | in-use | deleting | deleted | error 103 switch self.Status { 104 case "creating": 105 return api.DISK_ALLOCATING 106 case "deleting": 107 return api.DISK_DEALLOC 108 case "error": 109 return api.DISK_ALLOC_FAILED 110 default: 111 return api.DISK_READY 112 } 113 } 114 115 func (self *SDisk) Refresh() error { 116 new, err := self.storage.zone.region.GetDisk(self.DiskId) 117 if err != nil { 118 return err 119 } 120 return jsonutils.Update(self, new) 121 } 122 123 func (self *SDisk) IsEmulated() bool { 124 return false 125 } 126 127 func (self *SDisk) GetBillingType() string { 128 // todo: implement me 129 return billing.BILLING_TYPE_POSTPAID 130 } 131 132 func (self *SDisk) GetCreatedAt() time.Time { 133 return self.CreationTime 134 } 135 136 func (self *SDisk) GetExpiredAt() time.Time { 137 return self.ExpiredTime 138 } 139 140 func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 141 return self.storage, nil 142 } 143 144 func (self *SDisk) GetDiskFormat() string { 145 return "vhd" 146 } 147 148 func (self *SDisk) GetDiskSizeMB() int { 149 return self.Size * 1024 150 } 151 152 func (self *SDisk) GetIsAutoDelete() bool { 153 return self.DeleteWithInstance 154 } 155 156 func (self *SDisk) GetTemplateId() string { 157 return self.ImageId 158 } 159 160 func (self *SDisk) GetDiskType() string { 161 return self.Type 162 } 163 164 func (self *SDisk) GetFsFormat() string { 165 return "" 166 } 167 168 func (self *SDisk) GetIsNonPersistent() bool { 169 return false 170 } 171 172 func (self *SDisk) GetDriver() string { 173 return "scsi" 174 } 175 176 func (self *SDisk) GetCacheMode() string { 177 return "none" 178 } 179 180 func (self *SDisk) GetMountpoint() string { 181 return "" 182 } 183 184 func (self *SDisk) Delete(ctx context.Context) error { 185 if _, err := self.storage.zone.region.GetDisk(self.DiskId); err != nil && errors.Cause(err) == cloudprovider.ErrNotFound { 186 log.Errorf("Failed to find disk %s when delete", self.DiskId) 187 return nil 188 } 189 return self.storage.zone.region.DeleteDisk(self.DiskId) 190 } 191 192 func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { 193 if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc); err != nil { 194 log.Errorf("createSnapshot fail %s", err) 195 return nil, errors.Wrap(err, "CreateSnapshot") 196 } else if snapshot, err := self.getSnapshot(snapshotId); err != nil { 197 log.Errorf("getSnapshot %s", snapshotId) 198 return nil, errors.Wrap(err, "getSnapshot") 199 } else { 200 snapshot.region = self.storage.zone.region 201 if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil { 202 return nil, errors.Wrap(err, "WaitStatus.snapshot") 203 } 204 return snapshot, nil 205 } 206 } 207 208 func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { 209 if snapshot, err := self.getSnapshot(snapshotId); err != nil { 210 return nil, errors.Wrap(err, "getSnapshot") 211 } else { 212 snapshot.region = self.storage.zone.region 213 return snapshot, nil 214 } 215 } 216 217 func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 218 snapshots := make([]SSnapshot, 0) 219 for { 220 if parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, 0, 20); err != nil { 221 log.Errorf("GetSnapshots fail %s", err) 222 return nil, errors.Wrap(err, "GetSnapshots") 223 } else { 224 snapshots = append(snapshots, parts...) 225 if len(snapshots) >= total { 226 break 227 } 228 } 229 } 230 isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)) 231 for i := 0; i < len(snapshots); i++ { 232 snapshots[i].region = self.storage.zone.region 233 isnapshots[i] = &snapshots[i] 234 } 235 return isnapshots, nil 236 } 237 238 func (self *SDisk) Resize(ctx context.Context, newSizeMb int64) error { 239 err := self.storage.zone.region.resizeDisk(self.DiskId, newSizeMb) 240 if err != nil { 241 return err 242 } 243 244 return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 5*time.Second, 5*time.Second, 90*time.Second) 245 } 246 247 func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 248 return self.storage.zone.region.resetDisk(self.DiskId, snapshotId) 249 } 250 251 func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) { 252 if len(snapshotId) == 0 { 253 return nil, fmt.Errorf("GetSnapshot snapshot id should not be empty.") 254 } 255 256 if snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1); err != nil { 257 return nil, errors.Wrap(err, "GetSnapshots") 258 } else if total != 1 { 259 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetSnapshots") 260 } else { 261 return &snapshots[0], nil 262 } 263 } 264 265 func (self *SRegion) GetDisks(instanceId string, zoneId string, storageType string, diskIds []string, offset int, limit int) ([]SDisk, int, error) { 266 params := &ec2.DescribeVolumesInput{} 267 filters := make([]*ec2.Filter, 0) 268 if len(instanceId) > 0 { 269 filters = AppendSingleValueFilter(filters, "attachment.instance-id", instanceId) 270 } 271 272 if len(zoneId) > 0 { 273 filters = AppendSingleValueFilter(filters, "availability-zone", zoneId) 274 } 275 276 if len(storageType) > 0 { 277 filters = AppendSingleValueFilter(filters, "volume-type", storageType) 278 } 279 280 if len(filters) > 0 { 281 params.SetFilters(filters) 282 } 283 284 if len(diskIds) > 0 { 285 params.SetVolumeIds(ConvertedList(diskIds)) 286 } 287 288 ec2Client, err := self.getEc2Client() 289 if err != nil { 290 return nil, 0, errors.Wrap(err, "getEc2Client") 291 } 292 ret, err := ec2Client.DescribeVolumes(params) 293 if err != nil { 294 return nil, 0, errors.Wrap(err, "DescribeVolumes") 295 } 296 297 disks := []SDisk{} 298 for _, item := range ret.Volumes { 299 if err := FillZero(item); err != nil { 300 return nil, 0, err 301 } 302 303 tagspec := TagSpec{} 304 tagspec.LoadingEc2Tags(item.Tags) 305 306 disk := SDisk{} 307 disk.ZoneId = *item.AvailabilityZone 308 disk.Status = *item.State 309 disk.DiskName = tagspec.GetNameTag() 310 disk.Size = int(*item.Size) 311 disk.Category = *item.VolumeType 312 disk.RegionId = self.RegionId 313 disk.SourceSnapshotId = *item.SnapshotId 314 disk.Encrypted = *item.Encrypted 315 disk.DiskId = *item.VolumeId 316 disk.Iops = int(*item.Iops) 317 disk.CreationTime = *item.CreateTime 318 jsonutils.Update(&disk.AwsTags.TagSet, item.Tags) 319 if len(item.Attachments) > 0 { 320 disk.DeleteWithInstance = *item.Attachments[0].DeleteOnTermination 321 disk.AttachedTime = *item.Attachments[0].AttachTime 322 disk.AttachmentStatus = *item.Attachments[0].State 323 disk.Device = StrVal(item.Attachments[0].Device) 324 disk.InstanceId = StrVal(item.Attachments[0].InstanceId) 325 // todo: 需要通过describe-instances 的root device 判断是否是系统盘 326 // todo: 系统盘需要放在返回disks列表的首位 327 if len(disk.InstanceId) > 0 { 328 instance, err := self.GetInstance(disk.InstanceId) 329 if err != nil { 330 log.Debugf("%s", err) 331 return nil, 0, err 332 } 333 334 if disk.Device == instance.RootDeviceName { 335 disk.Type = api.DISK_TYPE_SYS 336 disk.ImageId = instance.ImageId 337 } else { 338 disk.Type = api.DISK_TYPE_DATA 339 } 340 } else { 341 disk.Type = api.DISK_TYPE_DATA 342 } 343 } 344 345 disks = append(disks, disk) 346 } 347 348 // 系统盘必须放在第零个位置 349 sort.Slice(disks, func(i, j int) bool { 350 if disks[i].Type == api.DISK_TYPE_SYS { 351 return true 352 } 353 354 if disks[j].Type != api.DISK_TYPE_SYS && disks[i].Device < disks[j].Device { 355 return true 356 } 357 358 return false 359 }) 360 361 return disks, len(disks), nil 362 } 363 364 func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { 365 if len(diskId) == 0 { 366 // return nil, fmt.Errorf("GetDisk diskId should not be empty.") 367 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetDisk") 368 } 369 disks, total, err := self.GetDisks("", "", "", []string{diskId}, 0, 1) 370 if err != nil { 371 if strings.Contains(err.Error(), "InvalidVolume.NotFound") { 372 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetDisks") 373 } else { 374 return nil, errors.Wrap(err, "GetDisks") 375 } 376 } 377 if total != 1 { 378 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetDisk") 379 } 380 return &disks[0], nil 381 } 382 383 func (self *SRegion) DeleteDisk(diskId string) error { 384 disk, err := self.GetDisk(diskId) 385 if err != nil { 386 return err 387 } 388 389 if disk.Status != ec2.VolumeStateAvailable { 390 return fmt.Errorf("disk status not in %s", ec2.VolumeStateAvailable) 391 } 392 params := &ec2.DeleteVolumeInput{} 393 if len(diskId) <= 0 { 394 return fmt.Errorf("disk id should not be empty") 395 } 396 397 params.SetVolumeId(diskId) 398 log.Debugf("DeleteDisk with params: %s", params.String()) 399 ec2Client, err := self.getEc2Client() 400 if err != nil { 401 return errors.Wrap(err, "getEc2Client") 402 } 403 _, err = ec2Client.DeleteVolume(params) 404 return err 405 } 406 407 func (self *SRegion) resizeDisk(diskId string, sizeMb int64) error { 408 // https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/volume_constraints.html 409 // MBR -> 2 TiB 410 // GPT -> 16 TiB 411 // size unit GiB 412 sizeGb := sizeMb / 1024 413 params := &ec2.ModifyVolumeInput{} 414 if sizeGb > 0 { 415 params.SetSize(sizeGb) 416 } else { 417 return fmt.Errorf("size should great than 0") 418 } 419 420 if len(diskId) <= 0 { 421 return fmt.Errorf("disk id should not be empty") 422 } else { 423 params.SetVolumeId(diskId) 424 } 425 426 ec2Client, err := self.getEc2Client() 427 if err != nil { 428 return errors.Wrap(err, "getEc2Client") 429 } 430 _, err = ec2Client.ModifyVolume(params) 431 return err 432 } 433 434 func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) { 435 // 这里实际是回滚快照 436 disk, err := self.GetDisk(diskId) 437 if err != nil { 438 log.Debugf("resetDisk %s:%s", diskId, err.Error()) 439 return "", err 440 } 441 442 params := &ec2.CreateVolumeInput{} 443 if len(snapshotId) > 0 { 444 params.SetSnapshotId(snapshotId) 445 } 446 params.SetSize(int64(disk.Size)) 447 params.SetVolumeType(disk.Category) 448 params.SetAvailabilityZone(disk.ZoneId) 449 //tags, _ := disk.Tags.GetTagSpecifications() 450 //params.SetTagSpecifications([]*ec2.TagSpecification{tags}) 451 452 ec2Client, err := self.getEc2Client() 453 if err != nil { 454 return "", errors.Wrap(err, "getEc2Client") 455 } 456 ret, err := ec2Client.CreateVolume(params) 457 if err != nil { 458 log.Debugf("resetDisk %s: %s", params.String(), err.Error()) 459 return "", err 460 } 461 462 // detach disk 463 if disk.Status == ec2.VolumeStateInUse { 464 err := self.DetachDisk(disk.InstanceId, diskId) 465 if err != nil { 466 log.Debugf("resetDisk %s %s: %s", disk.InstanceId, diskId, err.Error()) 467 return "", err 468 } 469 470 err = ec2Client.WaitUntilVolumeAvailable(&ec2.DescribeVolumesInput{VolumeIds: []*string{&diskId}}) 471 if err != nil { 472 log.Debugf("resetDisk :%s", err.Error()) 473 return "", err 474 } 475 } 476 477 err = self.AttachDisk(disk.InstanceId, *ret.VolumeId, disk.Device) 478 if err != nil { 479 log.Debugf("resetDisk %s %s %s: %s", disk.InstanceId, *ret.VolumeId, disk.Device, err.Error()) 480 return "", err 481 } 482 483 // 绑定成功后删除原磁盘 484 return StrVal(ret.VolumeId), self.DeleteDisk(diskId) 485 } 486 487 // io1类型的卷需要指定IOPS参数,最大不超过32000。这里根据aws网站的建议值进行设置 488 // io2类型的卷需要指定IOPS参数,最大不超过64000。 489 // GenDiskIops Base 100, 卷每增加2G。IOPS增加1。最多到3000 iops 490 func GenDiskIops(diskType string, sizeGB int) int64 { 491 if diskType == api.STORAGE_IO1_SSD || diskType == api.STORAGE_IO2_SSD { 492 iops := int64(100 + sizeGB/2) 493 if iops < 3000 { 494 return iops 495 } else { 496 return 3000 497 } 498 } 499 500 return 0 501 } 502 503 func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string) (string, error) { 504 tagspec := TagSpec{ResourceType: "volume"} 505 tagspec.SetNameTag(name) 506 tagspec.SetDescTag(desc) 507 ec2Tags, _ := tagspec.GetTagSpecifications() 508 509 params := &ec2.CreateVolumeInput{} 510 params.SetAvailabilityZone(zoneId) 511 params.SetVolumeType(category) 512 params.SetSize(int64(sizeGb)) 513 if len(snapshotId) > 0 { 514 params.SetSnapshotId(snapshotId) 515 } 516 517 if iops := GenDiskIops(category, sizeGb); iops > 0 { 518 params.SetIops(iops) 519 } 520 521 params.SetTagSpecifications([]*ec2.TagSpecification{ec2Tags}) 522 523 ec2Client, err := self.getEc2Client() 524 if err != nil { 525 return "", errors.Wrap(err, "getEc2Client") 526 } 527 ret, err := ec2Client.CreateVolume(params) 528 if err != nil { 529 return "", err 530 } 531 532 paramsWait := &ec2.DescribeVolumesInput{} 533 paramsWait.SetVolumeIds([]*string{ret.VolumeId}) 534 err = ec2Client.WaitUntilVolumeAvailable(paramsWait) 535 if err != nil { 536 return "", err 537 } 538 return StrVal(ret.VolumeId), nil 539 } 540 541 func (disk *SDisk) GetAccessPath() string { 542 return "" 543 } 544 545 func (self *SDisk) Rebuild(ctx context.Context) error { 546 _, err := self.storage.zone.region.resetDisk(self.DiskId, "") 547 return err 548 } 549 550 func (self *SDisk) GetProjectId() string { 551 return "" 552 }