yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ctyun/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 ctyun 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "time" 22 23 "yunion.io/x/jsonutils" 24 "yunion.io/x/log" 25 "yunion.io/x/pkg/errors" 26 "yunion.io/x/pkg/utils" 27 28 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/cloudmux/pkg/cloudprovider" 31 "yunion.io/x/cloudmux/pkg/multicloud" 32 ) 33 34 // http://ctyun-api-url/apiproxy/v3/ondemand/queryVolumes 35 type SDisk struct { 36 storage *SStorage 37 multicloud.SDisk 38 CtyunTags 39 multicloud.SBillingBase 40 41 diskDetails *DiskDetails 42 43 ID string `json:"id"` 44 Status string `json:"status"` 45 Name string `json:"name"` 46 CreatedAt int64 `json:"created_at"` 47 UpdatedAt string `json:"updated_at"` 48 Multiattach bool `json:"multiattach"` 49 ReplicationStatus string `json:"replication_status"` 50 SizeGB int64 `json:"size"` 51 Metadata Metadata `json:"metadata"` 52 VolumeType string `json:"volume_type"` 53 UserID string `json:"user_id"` 54 Shareable bool `json:"shareable"` 55 Encrypted bool `json:"encrypted"` 56 Bootable string `json:"bootable"` 57 AvailabilityZone string `json:"availability_zone"` 58 Attachments []Attachment `json:"attachments"` 59 MasterOrderID string `json:"masterOrderId"` 60 WorkOrderResourceID string `json:"workOrderResourceId"` 61 ExpireTime int64 `json:"expireTime"` 62 IsFreeze int64 `json:"isFreeze"` 63 } 64 65 type Attachment struct { 66 VolumeID string `json:"volume_id"` 67 AttachmentID string `json:"attachment_id"` 68 AttachedAt string `json:"attached_at"` 69 ServerID string `json:"server_id"` 70 Device string `json:"device"` 71 ID string `json:"id"` 72 } 73 74 type Metadata struct { 75 OrderID string `json:"orderID"` 76 AttachedMode string `json:"attached_mode"` 77 ResourceSpecCode string `json:"resourceSpecCode"` 78 ProductID string `json:"productID"` 79 Readonly string `json:"readonly"` 80 } 81 82 type DiskDetails struct { 83 ID string `json:"id"` 84 ResEbsID string `json:"resEbsId"` 85 Size int64 `json:"size"` 86 Name string `json:"name"` 87 RegionID string `json:"regionId"` 88 AccountID string `json:"accountId"` 89 UserID string `json:"userId"` 90 HostID string `json:"hostId"` 91 OrderID string `json:"orderId"` 92 Status int64 `json:"status"` 93 Type string `json:"type"` 94 VolumeStatus int64 `json:"volumeStatus"` 95 CreateDate int64 `json:"createDate"` 96 DueDate int64 `json:"dueDate"` 97 ZoneID string `json:"zoneId"` 98 ZoneName string `json:"zoneName"` 99 IsSysVolume int64 `json:"isSysVolume"` 100 IsPackaged int64 `json:"isPackaged"` 101 WorkOrderResourceID string `json:"workOrderResourceId"` 102 IsFreeze int64 `json:"isFreeze"` 103 } 104 105 func (self *SDisk) GetBillingType() string { 106 if self.ExpireTime > 0 { 107 return billing_api.BILLING_TYPE_PREPAID 108 } 109 110 return billing_api.BILLING_TYPE_POSTPAID 111 } 112 113 func (self *SDisk) GetCreatedAt() time.Time { 114 return time.Unix(self.CreatedAt/1000, 0) 115 } 116 117 func (self *SDisk) GetExpiredAt() time.Time { 118 if self.ExpireTime == 0 { 119 return time.Time{} 120 } 121 122 return time.Unix(self.ExpireTime/1000, 0) 123 } 124 125 func (self *SDisk) GetId() string { 126 return self.ID 127 } 128 129 func (self *SDisk) GetName() string { 130 if len(self.Name) > 0 { 131 return self.Name 132 } 133 134 return self.ID 135 } 136 137 func (self *SDisk) GetGlobalId() string { 138 return self.GetId() 139 } 140 141 func (self *SDisk) GetStatus() string { 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 new, err := self.storage.zone.region.GetDisk(self.GetId()) 180 if err != nil { 181 return err 182 } 183 return jsonutils.Update(self, new) 184 } 185 186 func (self *SDisk) IsEmulated() bool { 187 return false 188 } 189 190 func (self *SDisk) GetSysTags() map[string]string { 191 data := map[string]string{} 192 data["hypervisor"] = api.HYPERVISOR_CTYUN 193 return data 194 } 195 196 func (self *SDisk) GetProjectId() string { 197 return "" 198 } 199 200 func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 201 return self.storage, nil 202 } 203 204 func (self *SDisk) GetIStorageId() string { 205 return self.storage.GetId() 206 } 207 208 func (self *SDisk) GetDiskFormat() string { 209 return "vhd" 210 } 211 212 func (self *SDisk) GetDiskSizeMB() int { 213 return int(self.SizeGB * 1024) 214 } 215 216 func (self *SDisk) GetIsAutoDelete() bool { 217 if len(self.Attachments) == 0 { 218 return false 219 } 220 221 if self.Bootable == "true" { 222 return true 223 } 224 225 return false 226 } 227 228 func (self *SDisk) GetTemplateId() string { 229 if len(self.Attachments) > 0 && len(self.Attachments[0].ServerID) > 0 { 230 server, err := self.storage.zone.region.GetVMById(self.Attachments[0].ServerID) 231 if err != nil { 232 log.Errorf("SDisk.GetTemplateId %s", err) 233 return "" 234 } 235 236 image, err := server.GetImage() 237 if err != nil { 238 log.Errorf("SDisk.GetImage %s", err) 239 return "" 240 } 241 242 return image.GetId() 243 } 244 245 return "" 246 } 247 248 func (self *SDisk) GetDiskType() string { 249 if self.Bootable == "true" { 250 return api.DISK_TYPE_SYS 251 } else { 252 return api.DISK_TYPE_DATA 253 } 254 } 255 256 func (self *SDisk) GetFsFormat() string { 257 return "" 258 } 259 260 func (self *SDisk) GetIsNonPersistent() bool { 261 return false 262 } 263 264 func (self *SDisk) GetDriver() string { 265 return "scsi" 266 } 267 268 func (self *SDisk) GetCacheMode() string { 269 return "none" 270 } 271 272 func (self *SDisk) GetMountpoint() string { 273 if len(self.Attachments) > 0 { 274 return self.Attachments[0].Device 275 } 276 277 return "" 278 } 279 280 func (self *SDisk) GetAccessPath() string { 281 return "" 282 } 283 284 func (self *SDisk) Delete(ctx context.Context) error { 285 _, err := self.storage.zone.region.DeleteDisk(self.GetId()) 286 if err != nil { 287 return errors.Wrap(err, "SDisk.Delete") 288 } 289 290 return nil 291 } 292 293 func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { 294 return nil, cloudprovider.ErrNotSupported 295 } 296 297 // POST http://ctyun-api-url/apiproxy/v3/ondemand/createVBS 298 func (self *SDisk) GetISnapshot(idStr string) (cloudprovider.ICloudSnapshot, error) { 299 return nil, cloudprovider.ErrNotFound 300 } 301 302 // no snapshot api opened 303 func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 304 return []cloudprovider.ICloudSnapshot{}, nil 305 } 306 307 // POST http://ctyun-api-url/apiproxy/v3/ondemand/updateDiskBackupPolicy 308 func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { 309 return []string{}, nil 310 } 311 312 func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error { 313 jobId, err := self.storage.zone.region.ResizeDisk(self.GetId(), strconv.Itoa(int(newSizeMB/1024))) 314 if err != nil { 315 return errors.Wrap(err, "Disk.Resize") 316 } 317 318 err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { 319 statusJson, err := self.storage.zone.region.GetVolumeJob(jobId) 320 // ctyun 偶尔会报客户端错误,其实job已经到后台执行了 321 if err != nil { 322 log.Debugf("Ctyun.SDisk.Resize.GetVolumeJob %s", err) 323 return false, nil 324 } 325 326 if status, _ := statusJson.GetString("status"); status == "SUCCESS" { 327 return true, nil 328 } else if status == "FAILED" { 329 return false, fmt.Errorf("Resize job %s failed", jobId) 330 } else { 331 return false, nil 332 } 333 }) 334 if err != nil { 335 return errors.Wrap(err, "Disk.Resize.Wait") 336 } 337 338 return nil 339 } 340 341 func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 342 return "", cloudprovider.ErrNotSupported 343 } 344 345 func (self *SDisk) Rebuild(ctx context.Context) error { 346 return cloudprovider.ErrNotSupported 347 } 348 349 func (self *SDisk) GetDiskDetails() (*DiskDetails, error) { 350 if self.diskDetails != nil { 351 return self.diskDetails, nil 352 } 353 354 details, err := self.storage.zone.region.GetDiskDetailByDiskId(self.GetId()) 355 if err != nil { 356 return nil, errors.Wrap(err, "SDisk.GetDiskDetails.GetDiskDetailByDiskId") 357 } 358 359 self.diskDetails = details 360 return self.diskDetails, nil 361 } 362 363 func (self *SRegion) GetDisks() ([]SDisk, error) { 364 params := map[string]string{ 365 "regionId": self.GetId(), 366 } 367 368 resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params) 369 if err != nil { 370 return nil, errors.Wrap(err, "Region.GetDisks.DoGet") 371 } 372 373 disks := make([]SDisk, 0) 374 err = resp.Unmarshal(&disks, "returnObj", "volumes") 375 if err != nil { 376 return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal") 377 } 378 379 for i := range disks { 380 izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[i].AvailabilityZone)) 381 if err != nil { 382 return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById") 383 } 384 385 disks[i].storage = &SStorage{ 386 zone: izone.(*SZone), 387 storageType: disks[i].VolumeType, 388 } 389 } 390 391 return disks, nil 392 } 393 394 func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { 395 params := map[string]string{ 396 "regionId": self.GetId(), 397 "volumeId": diskId, 398 } 399 400 resp, err := self.client.DoGet("/apiproxy/v3/ondemand/queryVolumes", params) 401 if err != nil { 402 return nil, errors.Wrap(err, "Region.GetDisk.DoGet") 403 } 404 405 disks := make([]SDisk, 0) 406 err = resp.Unmarshal(&disks, "returnObj", "volumes") 407 if err != nil { 408 return nil, errors.Wrap(err, "Region.GetDisks.Unmarshal") 409 } 410 411 if len(disks) == 0 { 412 return nil, errors.Wrap(cloudprovider.ErrNotFound, "SRegion.GetDisk") 413 } else if len(disks) == 1 { 414 izone, err := self.GetIZoneById(getZoneGlobalId(self, disks[0].AvailabilityZone)) 415 if err != nil { 416 return nil, errors.Wrap(err, "SRegion.GetDisk.GetIZoneById") 417 } 418 419 disks[0].storage = &SStorage{ 420 zone: izone.(*SZone), 421 storageType: disks[0].VolumeType, 422 } 423 424 return &disks[0], nil 425 } else { 426 return nil, errors.Wrap(cloudprovider.ErrDuplicateId, "SRegion.GetDisk") 427 } 428 } 429 430 func (self *SRegion) GetDiskDetailByDiskId(diskId string) (*DiskDetails, error) { 431 params := map[string]string{ 432 "volumeId": diskId, 433 "regionId": self.GetId(), 434 } 435 436 resp, err := self.client.DoGet("/apiproxy/v3/queryDataDiskDetail", params) 437 if err != nil { 438 return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.DoGet") 439 } 440 441 disk := &DiskDetails{} 442 err = resp.Unmarshal(disk, "returnObj") 443 if err != nil { 444 return nil, errors.Wrap(err, "Region.GetDiskDetailByDiskId.Unmarshal") 445 } 446 447 return disk, nil 448 } 449 450 func (self *SRegion) CreateDisk(zoneId, name, diskType, size string) (*SDisk, error) { 451 diskParams := jsonutils.NewDict() 452 diskParams.Set("regionId", jsonutils.NewString(self.GetId())) 453 diskParams.Set("zoneId", jsonutils.NewString(zoneId)) 454 diskParams.Set("name", jsonutils.NewString(name)) 455 diskParams.Set("type", jsonutils.NewString(diskType)) 456 diskParams.Set("size", jsonutils.NewString(size)) 457 diskParams.Set("count", jsonutils.NewString("1")) 458 459 params := map[string]jsonutils.JSONObject{ 460 "createVolumeInfo": diskParams, 461 } 462 463 disks, err := self.GetDisks() 464 if err != nil { 465 return nil, errors.Wrap(err, "SRegion.CreateDisk.GetDisks") 466 } 467 468 diskIds := []string{} 469 for i := range disks { 470 diskIds = append(diskIds, disks[i].GetId()) 471 } 472 473 _, err = self.client.DoPost("/apiproxy/v3/ondemand/createVolume", params) 474 if err != nil { 475 return nil, errors.Wrap(err, "Region.CreateDisk.DoPost") 476 } 477 478 // 查询job结果一直报错,目前先用替代办法查找新硬盘ID,可能不准确。后续需要替换其它方法 479 diskId := "" 480 cloudprovider.Wait(3*time.Second, 300*time.Second, func() (b bool, err error) { 481 disks, err := self.GetDisks() 482 if err != nil { 483 return false, err 484 } 485 486 for i := range disks { 487 if !utils.IsInStringArray(disks[i].GetId(), diskIds) { 488 diskId = disks[i].GetId() 489 return true, nil 490 } 491 } 492 493 return false, nil 494 }) 495 496 return self.GetDisk(diskId) 497 } 498 499 func (self *SRegion) CreateDiskBackup(name, volumeId, desc string) (string, error) { 500 params := map[string]jsonutils.JSONObject{ 501 "regionId": jsonutils.NewString(self.GetId()), 502 "volumeId": jsonutils.NewString(volumeId), 503 "name": jsonutils.NewString(name), 504 "description": jsonutils.NewString(desc), 505 } 506 507 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVBS", params) 508 if err != nil { 509 return "", errors.Wrap(err, "Region.CreateDiskBackup.DoPost") 510 } 511 512 var jobId string 513 err = resp.Unmarshal(&jobId, "returnObj", "data") 514 if err != nil { 515 return "", errors.Wrap(err, "Region.CreateDiskBackup.Unmarshal") 516 } 517 518 return jobId, nil 519 } 520 521 func (self *SRegion) DeleteDisk(volumeId string) (string, error) { 522 params := map[string]jsonutils.JSONObject{ 523 "regionId": jsonutils.NewString(self.GetId()), 524 "volumeId": jsonutils.NewString(volumeId), 525 } 526 527 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVolume", params) 528 if err != nil { 529 msg, _ := resp.GetString("message") 530 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DeleteDisk.DoPost") 531 } 532 533 var jobId string 534 err = resp.Unmarshal(&jobId, "returnObj", "data") 535 if err != nil { 536 return "", errors.Wrap(err, "SRegion.DeleteDisk.Unmarshal") 537 } 538 539 return jobId, nil 540 } 541 542 func (self *SRegion) ResizeDisk(volumeId string, newSizeGB string) (string, error) { 543 params := map[string]jsonutils.JSONObject{ 544 "regionId": jsonutils.NewString(self.GetId()), 545 "volumeId": jsonutils.NewString(volumeId), 546 "newSize": jsonutils.NewString(newSizeGB), 547 } 548 549 resp, err := self.client.DoPost("/apiproxy/v3/ondemand/expandVolumeSize", params) 550 if err != nil { 551 return "", errors.Wrap(err, "SRegion.ResizeDisk.DoPost") 552 } 553 554 var ok bool 555 err = resp.Unmarshal(&ok, "returnObj", "status") 556 if !ok { 557 msg, _ := resp.GetString("message") 558 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ResizeDisk.JobFailed") 559 } 560 561 var jobId string 562 err = resp.Unmarshal(&jobId, "returnObj", "data") 563 if err != nil { 564 return "", errors.Wrap(err, "SRegion.ResizeDisk.Unmarshal") 565 } 566 567 return jobId, nil 568 } 569 570 func (self *SRegion) RestoreDisk(volumeId, backupId string) (string, error) { 571 diskBackupParams := jsonutils.NewDict() 572 diskBackupParams.Set("regionId", jsonutils.NewString(self.GetId())) 573 diskBackupParams.Set("backupId", jsonutils.NewString(backupId)) 574 diskBackupParams.Set("volumeId", jsonutils.NewString(volumeId)) 575 576 params := map[string]jsonutils.JSONObject{ 577 "diskBackup": diskBackupParams, 578 } 579 580 resp, err := self.client.DoPost("/apiproxy/v3/restoreDiskBackup", params) 581 if err != nil { 582 return "", errors.Wrap(err, "SRegion.RestoreDisk.DoPost") 583 } 584 585 var ok bool 586 err = resp.Unmarshal(&ok, "returnObj", "status") 587 if !ok { 588 msg, _ := resp.GetString("message") 589 return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RestoreDisk.JobFailed") 590 } 591 592 var jobId string 593 err = resp.Unmarshal(&jobId, "returnObj", "data") 594 if err != nil { 595 return "", errors.Wrap(err, "SRegion.RestoreDisk.Unmarshal") 596 } 597 598 return jobId, nil 599 }