yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/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 hcs 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 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 multicloud.SDisk 94 huawei.HuaweiDiskTags 95 //details *SResourceDetail 96 97 region *SRegion 98 99 Id string `json:"id"` 100 Name string `json:"name"` 101 Status string `json:"status"` 102 Attachments []Attachment `json:"attachments"` 103 Description string `json:"description"` 104 SizeGB int `json:"size"` 105 Metadata DiskMeta `json:"metadata"` 106 Encrypted bool `json:"encrypted"` 107 Bootable string `json:"bootable"` 108 Multiattach bool `json:"multiattach"` 109 AvailabilityZone string `json:"availability_zone"` 110 SourceVolid string `json:"source_volid"` 111 SnapshotId string `json:"snapshot_id"` 112 CreatedAt time.Time `json:"created_at"` 113 VolumeType string `json:"volume_type"` 114 VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"` 115 ReplicationStatus string `json:"replication_status"` 116 UserId string `json:"user_id"` 117 ConsistencygroupId string `json:"consistencygroup_id"` 118 UpdatedAt string `json:"updated_at"` 119 EnterpriseProjectId string 120 121 ExpiredTime time.Time 122 } 123 124 func (self *SDisk) GetId() string { 125 return self.Id 126 } 127 128 func (self *SDisk) GetName() string { 129 if len(self.Name) == 0 { 130 return self.Id 131 } 132 133 return self.Name 134 } 135 136 func (self *SDisk) GetGlobalId() string { 137 return self.Id 138 } 139 140 func (self *SDisk) GetStatus() string { 141 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html 142 switch self.Status { 143 case "creating", "downloading": 144 return api.DISK_ALLOCATING 145 case "available", "in-use": 146 return api.DISK_READY 147 case "error": 148 return api.DISK_ALLOC_FAILED 149 case "attaching": 150 return api.DISK_ATTACHING 151 case "detaching": 152 return api.DISK_DETACHING 153 case "restoring-backup": 154 return api.DISK_REBUILD 155 case "backing-up": 156 return api.DISK_BACKUP_STARTALLOC // ? 157 case "error_restoring": 158 return api.DISK_BACKUP_ALLOC_FAILED 159 case "uploading": 160 return api.DISK_SAVING //? 161 case "extending": 162 return api.DISK_RESIZING 163 case "error_extending": 164 return api.DISK_ALLOC_FAILED // ? 165 case "deleting": 166 return api.DISK_DEALLOC //? 167 case "error_deleting": 168 return api.DISK_DEALLOC_FAILED // ? 169 case "rollbacking": 170 return api.DISK_REBUILD 171 case "error_rollbacking": 172 return api.DISK_UNKNOWN 173 default: 174 return api.DISK_UNKNOWN 175 } 176 } 177 178 func (self *SDisk) Refresh() error { 179 ret, err := self.region.GetDisk(self.Id) 180 if err != nil { 181 return err 182 } 183 return jsonutils.Update(self, ret) 184 } 185 186 func (self *SDisk) GetBillingType() string { 187 return billing_api.BILLING_TYPE_POSTPAID 188 } 189 190 func (self *SDisk) GetCreatedAt() time.Time { 191 return self.CreatedAt 192 } 193 194 func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 195 return nil, cloudprovider.ErrNotImplemented 196 // return self.storage, nil 197 } 198 199 func (self *SDisk) GetDiskFormat() string { 200 return "vhd" 201 } 202 203 func (self *SDisk) GetDiskSizeMB() int { 204 return int(self.SizeGB * 1024) 205 } 206 207 func (self *SDisk) GetIsAutoDelete() bool { 208 return false 209 } 210 211 func (self *SDisk) GetTemplateId() string { 212 return self.VolumeImageMetadata.ImageId 213 } 214 215 // Bootable 表示硬盘是否为启动盘。 216 // 启动盘 != 系统盘(必须是启动盘且挂载在root device上) 217 func (self *SDisk) GetDiskType() string { 218 if self.Bootable == "true" { 219 return api.DISK_TYPE_SYS 220 } 221 return api.DISK_TYPE_DATA 222 } 223 224 func (self *SDisk) GetFsFormat() string { 225 return "" 226 } 227 228 func (self *SDisk) GetIsNonPersistent() bool { 229 return false 230 } 231 232 func (self *SDisk) GetDriver() string { 233 return "scsi" 234 } 235 236 func (self *SDisk) GetCacheMode() string { 237 return "none" 238 } 239 240 func (self *SDisk) GetMountpoint() string { 241 if len(self.Attachments) > 0 { 242 return self.Attachments[0].Device 243 } 244 return "" 245 } 246 247 func (self *SDisk) GetMountServerId() string { 248 if len(self.Attachments) > 0 { 249 return self.Attachments[0].ServerId 250 } 251 return "" 252 } 253 254 func (self *SDisk) GetAccessPath() string { 255 return "" 256 } 257 258 func (self *SDisk) Delete(ctx context.Context) error { 259 return self.region.DeleteDisk(self.Id) 260 } 261 262 func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { 263 snap, err := self.region.CreateSnapshot(self.Id, name, desc) 264 if err != nil { 265 return nil, err 266 } 267 return snap, nil 268 } 269 270 func (self *SDisk) GetISnapshot(id string) (cloudprovider.ICloudSnapshot, error) { 271 snapshot, err := self.region.GetSnapshot(id) 272 if err != nil { 273 return nil, err 274 } 275 return snapshot, nil 276 } 277 278 func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 279 snapshots, err := self.region.GetSnapshots(self.Id, "") 280 if err != nil { 281 return nil, err 282 } 283 ret := []cloudprovider.ICloudSnapshot{} 284 for i := 0; i < len(snapshots); i++ { 285 snapshots[i].region = self.region 286 ret = append(ret, &snapshots[i]) 287 } 288 return ret, nil 289 } 290 291 func (self *SDisk) Resize(ctx context.Context, sizeMB int64) error { 292 return self.region.ResizeDisk(self.GetId(), sizeMB/1024) 293 } 294 295 func (self *SDisk) Detach() error { 296 return self.region.DetachDisk(self.GetMountServerId(), self.GetId()) 297 } 298 299 func (self *SDisk) Attach(device string) error { 300 return self.region.AttachDisk(self.GetMountServerId(), self.GetId(), device) 301 } 302 303 // 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html 304 // 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载 305 func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 306 mountpoint := self.GetMountpoint() 307 if len(mountpoint) > 0 { 308 err := self.Detach() 309 if err != nil { 310 return "", err 311 } 312 } 313 314 err := self.region.ResetDisk(self.Id, snapshotId) 315 if err != nil { 316 return self.Id, err 317 } 318 err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second) 319 if err != nil { 320 return "", err 321 } 322 if len(mountpoint) > 0 { 323 err := self.Attach(mountpoint) 324 if err != nil { 325 return "", err 326 } 327 } 328 return self.Id, nil 329 } 330 331 // 华为云不支持重置 332 func (self *SDisk) Rebuild(ctx context.Context) error { 333 return cloudprovider.ErrNotSupported 334 } 335 336 func (self *SRegion) GetDisk(id string) (*SDisk, error) { 337 ret := &SDisk{region: self} 338 res := fmt.Sprintf("volumes/%s", id) 339 return ret, self.evsGet(res, ret) 340 } 341 342 // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html 343 func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) { 344 params := url.Values{} 345 if len(zoneId) > 0 { 346 params.Set("availability_zone", zoneId) 347 } 348 disks := []SDisk{} 349 return disks, self.evsList("volumes/detail", params, &disks) 350 } 351 352 func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (*SDisk, error) { 353 volume := map[string]interface{}{ 354 "name": name, 355 "availability_zone": zoneId, 356 "description": desc, 357 "volume_type": category, 358 "size": sizeGb, 359 } 360 if len(snapshotId) > 0 { 361 volume["snapshot_id"] = snapshotId 362 } 363 if len(projectId) > 0 { 364 volume["enterprise_project_id"] = projectId 365 } 366 params := map[string]interface{}{ 367 "volume": volume, 368 } 369 ret := &SDisk{region: self} 370 return ret, self.evsCreate("volumes", params, ret) 371 } 372 373 func (self *SRegion) DeleteDisk(id string) error { 374 res := fmt.Sprintf("volumes/%s", id) 375 return self.evsDelete(res) 376 } 377 378 func (self *SRegion) ResizeDisk(id string, sizeGb int64) error { 379 params := map[string]interface{}{ 380 "os_extend": map[string]interface{}{ 381 "new_size": sizeGb, 382 }, 383 } 384 res := fmt.Sprintf("volumes/%s", id) 385 return self.evsPerform(res, "action", params) 386 } 387 388 func (self *SRegion) ResetDisk(diskId, snapshotId string) error { 389 params := map[string]interface{}{ 390 "volume_id": diskId, 391 } 392 res := fmt.Sprintf("os-vendor-snapshots/%s", snapshotId) 393 return self.evsPerform(res, "rollback", params) 394 } 395 396 func (self *SDisk) GetProjectId() string { 397 return self.EnterpriseProjectId 398 } 399 400 func (self *SRegion) DetachDisk(instanceId string, diskId string) error { 401 res := fmt.Sprintf("servers/%s/os-volume_attachments/%s", instanceId, diskId) 402 return self.delete("ecs", "v2", res) 403 } 404 405 func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error { 406 params := map[string]interface{}{ 407 "volumeAttachment": map[string]interface{}{ 408 "volumeId": instanceId, 409 "device": device, 410 }, 411 } 412 res := fmt.Sprintf("servers/%s/os-volume_attachments", instanceId) 413 return self.perform("ecs", "v2", res, "action", params, nil) 414 } 415 416 type SDiskType struct { 417 ExtraSpecs ExtraSpecs `json:"extra_specs"` 418 Name string `json:"name"` 419 QosSpecsId string `json:"qos_specs_id"` 420 Id string `json:"id"` 421 IsPublic bool `json:"is_public"` 422 } 423 424 func (self *SDiskType) IsAvaliableInZone(zoneId string) bool { 425 if strings.Contains(self.ExtraSpecs.HwAvailabilityZone, zoneId) { 426 return true 427 } 428 return false 429 } 430 431 type ExtraSpecs struct { 432 VolumeBackendName string `json:"volume_backend_name"` 433 HwAvailabilityZone string `json:"HW:availability-zone"` 434 } 435 436 func (self *SRegion) GetDiskTypes() ([]SDiskType, error) { 437 ret := []SDiskType{} 438 return ret, self.get("evs", "v3", "types", &ret) 439 }