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