yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/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 huawei 16 17 import ( 18 "context" 19 "strings" 20 "time" 21 22 "yunion.io/x/jsonutils" 23 "yunion.io/x/log" 24 "yunion.io/x/pkg/errors" 25 26 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 27 api "yunion.io/x/cloudmux/pkg/apis/compute" 28 "yunion.io/x/cloudmux/pkg/cloudprovider" 29 "yunion.io/x/cloudmux/pkg/multicloud" 30 ) 31 32 /* 33 华为云云硬盘 34 ======创建========== 35 1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区 36 2.计费模式 包年包月/按需计费 37 3.*支持自动备份 38 39 40 共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html 41 根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。 42 一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。 43 单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。 44 */ 45 46 type Attachment struct { 47 ServerID string `json:"server_id"` 48 AttachmentID string `json:"attachment_id"` 49 AttachedAt string `json:"attached_at"` 50 HostName string `json:"host_name"` 51 VolumeID string `json:"volume_id"` 52 Device string `json:"device"` 53 ID string `json:"id"` 54 } 55 56 type DiskMeta struct { 57 ResourceSpecCode string `json:"resourceSpecCode"` 58 Billing string `json:"billing"` 59 ResourceType string `json:"resourceType"` 60 AttachedMode string `json:"attached_mode"` 61 Readonly string `json:"readonly"` 62 } 63 64 type VolumeImageMetadata struct { 65 QuickStart string `json:"__quick_start"` 66 ContainerFormat string `json:"container_format"` 67 MinRAM string `json:"min_ram"` 68 ImageName string `json:"image_name"` 69 ImageID string `json:"image_id"` 70 OSType string `json:"__os_type"` 71 OSFeatureList string `json:"__os_feature_list"` 72 MinDisk string `json:"min_disk"` 73 SupportKVM string `json:"__support_kvm"` 74 VirtualEnvType string `json:"virtual_env_type"` 75 SizeGB string `json:"size"` 76 OSVersion string `json:"__os_version"` 77 OSBit string `json:"__os_bit"` 78 SupportKVMHi1822Hiovs string `json:"__support_kvm_hi1822_hiovs"` 79 SupportXen string `json:"__support_xen"` 80 Description string `json:"__description"` 81 Imagetype string `json:"__imagetype"` 82 DiskFormat string `json:"disk_format"` 83 ImageSourceType string `json:"__image_source_type"` 84 Checksum string `json:"checksum"` 85 Isregistered string `json:"__isregistered"` 86 HwVifMultiqueueEnabled string `json:"hw_vif_multiqueue_enabled"` 87 Platform string `json:"__platform"` 88 } 89 90 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html 91 type SDisk struct { 92 storage *SStorage 93 multicloud.SDisk 94 HuaweiDiskTags 95 details *SResourceDetail 96 97 ID string `json:"id"` 98 Name string `json:"name"` 99 Status string `json:"status"` 100 Attachments []Attachment `json:"attachments"` 101 Description string `json:"description"` 102 SizeGB int `json:"size"` 103 Metadata DiskMeta `json:"metadata"` 104 Encrypted bool `json:"encrypted"` 105 Bootable string `json:"bootable"` 106 Multiattach bool `json:"multiattach"` 107 AvailabilityZone string `json:"availability_zone"` 108 SourceVolid string `json:"source_volid"` 109 SnapshotID string `json:"snapshot_id"` 110 CreatedAt time.Time `json:"created_at"` 111 VolumeType string `json:"volume_type"` 112 VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"` 113 ReplicationStatus string `json:"replication_status"` 114 UserID string `json:"user_id"` 115 ConsistencygroupID string `json:"consistencygroup_id"` 116 UpdatedAt string `json:"updated_at"` 117 EnterpriseProjectId string 118 119 ExpiredTime time.Time 120 } 121 122 func (self *SDisk) GetId() string { 123 return self.ID 124 } 125 126 func (self *SDisk) GetName() string { 127 if len(self.Name) == 0 { 128 return self.ID 129 } 130 131 return self.Name 132 } 133 134 func (self *SDisk) GetGlobalId() string { 135 return self.ID 136 } 137 138 func (self *SDisk) GetStatus() string { 139 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html 140 switch self.Status { 141 case "creating", "downloading": 142 return api.DISK_ALLOCATING 143 case "available", "in-use": 144 return api.DISK_READY 145 case "error": 146 return api.DISK_ALLOC_FAILED 147 case "attaching": 148 return api.DISK_ATTACHING 149 case "detaching": 150 return api.DISK_DETACHING 151 case "restoring-backup": 152 return api.DISK_REBUILD 153 case "backing-up": 154 return api.DISK_BACKUP_STARTALLOC // ? 155 case "error_restoring": 156 return api.DISK_BACKUP_ALLOC_FAILED 157 case "uploading": 158 return api.DISK_SAVING //? 159 case "extending": 160 return api.DISK_RESIZING 161 case "error_extending": 162 return api.DISK_ALLOC_FAILED // ? 163 case "deleting": 164 return api.DISK_DEALLOC //? 165 case "error_deleting": 166 return api.DISK_DEALLOC_FAILED // ? 167 case "rollbacking": 168 return api.DISK_REBUILD 169 case "error_rollbacking": 170 return api.DISK_UNKNOWN 171 default: 172 return api.DISK_UNKNOWN 173 } 174 } 175 176 func (self *SDisk) Refresh() error { 177 new, err := self.storage.zone.region.GetDisk(self.GetId()) 178 if err != nil { 179 return err 180 } 181 return jsonutils.Update(self, new) 182 } 183 184 func (self *SDisk) IsEmulated() bool { 185 return false 186 } 187 188 func (self *SDisk) getResourceDetails() *SResourceDetail { 189 if self.details != nil { 190 return self.details 191 } 192 193 res, err := self.storage.zone.region.GetOrderResourceDetail(self.GetId()) 194 if err != nil { 195 log.Debugln(err) 196 return nil 197 } 198 199 self.details = &res 200 return self.details 201 } 202 203 func (self *SDisk) GetBillingType() string { 204 details := self.getResourceDetails() 205 if details == nil { 206 return billing_api.BILLING_TYPE_POSTPAID 207 } else { 208 return billing_api.BILLING_TYPE_PREPAID 209 } 210 } 211 212 func (self *SDisk) GetCreatedAt() time.Time { 213 return self.CreatedAt 214 } 215 216 func (self *SDisk) GetExpiredAt() time.Time { 217 var expiredTime time.Time 218 details := self.getResourceDetails() 219 if details != nil { 220 expiredTime = details.ExpireTime 221 } 222 223 return expiredTime 224 } 225 226 func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 227 return self.storage, nil 228 } 229 230 func (self *SDisk) GetDiskFormat() string { 231 // self.volume_type ? 232 return "vhd" 233 } 234 235 func (self *SDisk) GetDiskSizeMB() int { 236 return int(self.SizeGB * 1024) 237 } 238 239 func (self *SDisk) checkAutoDelete(attachments []Attachment) bool { 240 autodelete := false 241 for _, attach := range attachments { 242 if len(attach.ServerID) > 0 { 243 // todo : 忽略错误?? 244 vm, err := self.storage.zone.region.GetInstanceByID(attach.ServerID) 245 if err != nil { 246 volumes := vm.OSExtendedVolumesVolumesAttached 247 for _, vol := range volumes { 248 if vol.ID == self.ID && strings.ToLower(vol.DeleteOnTermination) == "true" { 249 autodelete = true 250 } 251 } 252 } 253 254 break 255 } 256 } 257 258 return autodelete 259 } 260 261 func (self *SDisk) GetIsAutoDelete() bool { 262 if len(self.Attachments) > 0 { 263 return self.checkAutoDelete(self.Attachments) 264 } 265 266 return false 267 } 268 269 func (self *SDisk) GetTemplateId() string { 270 return self.VolumeImageMetadata.ImageID 271 } 272 273 // Bootable 表示硬盘是否为启动盘。 274 // 启动盘 != 系统盘(必须是启动盘且挂载在root device上) 275 func (self *SDisk) GetDiskType() string { 276 if self.Bootable == "true" { 277 return api.DISK_TYPE_SYS 278 } else { 279 return api.DISK_TYPE_DATA 280 } 281 } 282 283 func (self *SDisk) GetFsFormat() string { 284 return "" 285 } 286 287 func (self *SDisk) GetIsNonPersistent() bool { 288 return false 289 } 290 291 func (self *SDisk) GetDriver() string { 292 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762431.html 293 // scsi or vbd? 294 // todo: implement me 295 return "scsi" 296 } 297 298 func (self *SDisk) GetCacheMode() string { 299 return "none" 300 } 301 302 func (self *SDisk) GetMountpoint() string { 303 if len(self.Attachments) > 0 { 304 return self.Attachments[0].Device 305 } 306 307 return "" 308 } 309 310 func (self *SDisk) GetMountServerId() string { 311 if len(self.Attachments) > 0 { 312 return self.Attachments[0].ServerID 313 } 314 315 return "" 316 } 317 318 func (self *SDisk) GetAccessPath() string { 319 return "" 320 } 321 322 func (self *SDisk) Delete(ctx context.Context) error { 323 disk, err := self.storage.zone.region.GetDisk(self.GetId()) 324 if err != nil { 325 if errors.Cause(err) == cloudprovider.ErrNotFound { 326 return nil 327 } 328 return err 329 } 330 if disk.Status != "deleting" { 331 // 等待硬盘ready 332 cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second) 333 err := self.storage.zone.region.DeleteDisk(self.GetId()) 334 if err != nil { 335 return err 336 } 337 } 338 339 return cloudprovider.WaitDeleted(self, 10*time.Second, 120*time.Second) 340 } 341 342 func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { 343 if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc); err != nil { 344 log.Errorf("createSnapshot fail %s", err) 345 return nil, err 346 } else if snapshot, err := self.getSnapshot(snapshotId); err != nil { 347 return nil, err 348 } else { 349 snapshot.region = self.storage.zone.region 350 if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil { 351 return nil, err 352 } 353 return snapshot, nil 354 } 355 } 356 357 func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) { 358 snapshot, err := self.storage.zone.region.GetSnapshotById(snapshotId) 359 return &snapshot, err 360 } 361 362 func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { 363 snapshot, err := self.getSnapshot(snapshotId) 364 return snapshot, err 365 } 366 367 func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 368 snapshots, err := self.storage.zone.region.GetSnapshots(self.ID, "") 369 if err != nil { 370 return nil, err 371 } 372 373 isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)) 374 for i := 0; i < len(snapshots); i++ { 375 isnapshots[i] = &snapshots[i] 376 } 377 return isnapshots, nil 378 } 379 380 func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error { 381 err := cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second) 382 if err != nil { 383 return err 384 } 385 386 sizeGb := newSizeMB / 1024 387 err = self.storage.zone.region.resizeDisk(self.GetId(), sizeGb) 388 if err != nil { 389 return err 390 } 391 392 return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 15*time.Second, 5*time.Second, 60*time.Second) 393 } 394 395 func (self *SDisk) Detach() error { 396 err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId()) 397 if err != nil { 398 log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err) 399 return err 400 } 401 402 return cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool { 403 err := self.Refresh() 404 if err != nil { 405 log.Debugln(err) 406 return false 407 } 408 409 if self.Status == "available" { 410 return true 411 } 412 413 return false 414 }) 415 } 416 417 func (self *SDisk) Attach(device string) error { 418 err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device) 419 if err != nil { 420 log.Debugf("attach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err) 421 return err 422 } 423 424 return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 10*time.Second, 5*time.Second, 60*time.Second) 425 } 426 427 // 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html 428 // 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载 429 func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 430 mountpoint := self.GetMountpoint() 431 if len(mountpoint) > 0 { 432 err := self.Detach() 433 if err != nil { 434 return "", err 435 } 436 } 437 438 diskId, err := self.storage.zone.region.resetDisk(self.GetId(), snapshotId) 439 if err != nil { 440 return diskId, err 441 } 442 443 err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second) 444 if err != nil { 445 return "", err 446 } 447 448 if len(mountpoint) > 0 { 449 err := self.Attach(mountpoint) 450 if err != nil { 451 return "", err 452 } 453 } 454 455 return diskId, nil 456 } 457 458 // 华为云不支持重置 459 func (self *SDisk) Rebuild(ctx context.Context) error { 460 return cloudprovider.ErrNotSupported 461 } 462 463 func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { 464 if len(diskId) == 0 { 465 return nil, cloudprovider.ErrNotFound 466 } 467 var disk SDisk 468 err := DoGet(self.ecsClient.Disks.Get, diskId, nil, &disk) 469 return &disk, err 470 } 471 472 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html 473 func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) { 474 queries := map[string]string{} 475 if len(zoneId) > 0 { 476 queries["availability_zone"] = zoneId 477 } 478 479 disks := make([]SDisk, 0) 480 err := doListAllWithOffset(self.ecsClient.Disks.List, queries, &disks) 481 return disks, err 482 } 483 484 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html 485 func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (string, error) { 486 params := jsonutils.NewDict() 487 volumeObj := jsonutils.NewDict() 488 volumeObj.Add(jsonutils.NewString(name), "name") 489 volumeObj.Add(jsonutils.NewString(zoneId), "availability_zone") 490 volumeObj.Add(jsonutils.NewString(desc), "description") 491 volumeObj.Add(jsonutils.NewString(category), "volume_type") 492 volumeObj.Add(jsonutils.NewInt(int64(sizeGb)), "size") 493 if len(snapshotId) > 0 { 494 volumeObj.Add(jsonutils.NewString(snapshotId), "snapshot_id") 495 } 496 if len(projectId) > 0 { 497 volumeObj.Add(jsonutils.NewString(projectId), "enterprise_project_id") 498 } 499 500 params.Add(volumeObj, "volume") 501 // 目前只支持创建按需资源,返回job id。 如果创建包年包月资源则返回order id 502 _id, err := self.ecsClient.Disks.AsyncCreate(params) 503 if err != nil { 504 log.Debugf("AsyncCreate with params: %s", params) 505 return "", errors.Wrap(err, "AsyncCreate") 506 } 507 508 // 按需计费 509 volumeId, err := self.GetTaskEntityID(self.ecsClient.Disks.ServiceType(), _id, "volume_id") 510 if err != nil { 511 return "", errors.Wrap(err, "GetAllSubTaskEntityIDs") 512 } 513 514 if len(volumeId) == 0 { 515 return "", errors.Errorf("CreateInstance job %s result is emtpy", _id) 516 } else { 517 return volumeId, nil 518 } 519 } 520 521 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762428.html 522 // 默认删除云硬盘关联的所有快照 523 func (self *SRegion) DeleteDisk(diskId string) error { 524 return DoDeleteWithSpec(self.ecsClient.Disks.DeleteInContextWithSpec, nil, diskId, "", nil, nil) 525 } 526 527 /* 528 扩容状态为available的云硬盘时,没有约束限制。 529 扩容状态为in-use的云硬盘时,有以下约束: 530 不支持共享云硬盘,即multiattach参数值必须为false。 531 云硬盘所挂载的云服务器状态必须为ACTIVE、PAUSED、SUSPENDED、SHUTOFF才支持扩容 532 */ 533 func (self *SRegion) resizeDisk(diskId string, sizeGB int64) error { 534 params := jsonutils.NewDict() 535 osExtendObj := jsonutils.NewDict() 536 osExtendObj.Add(jsonutils.NewInt(sizeGB), "new_size") // GB 537 params.Add(osExtendObj, "os-extend") 538 _, err := self.ecsClient.Disks.PerformAction2("action", diskId, params, "") 539 return err 540 } 541 542 /* 543 https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html 544 只支持快照回滚到源云硬盘,不支持快照回滚到其它指定云硬盘。 545 只有云硬盘状态处于“available”或“error_rollbacking”状态才允许快照回滚到源云硬盘。 546 */ 547 func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) { 548 params := jsonutils.NewDict() 549 rollbackObj := jsonutils.NewDict() 550 rollbackObj.Add(jsonutils.NewString(diskId), "volume_id") 551 params.Add(rollbackObj, "rollback") 552 _, err := self.ecsClient.OsSnapshots.PerformAction2("rollback", snapshotId, params, "") 553 return diskId, err 554 } 555 556 func (self *SDisk) GetProjectId() string { 557 return self.EnterpriseProjectId 558 }