yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/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 qcloud 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strconv" 22 "strings" 23 "time" 24 25 "yunion.io/x/jsonutils" 26 "yunion.io/x/log" 27 "yunion.io/x/pkg/utils" 28 29 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 30 api "yunion.io/x/cloudmux/pkg/apis/compute" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 ) 34 35 type Placement struct { 36 ProjectId int 37 Zone string 38 } 39 40 type SDisk struct { 41 storage *SStorage 42 multicloud.SDisk 43 QcloudTags 44 45 Attached bool 46 AutoRenewFlagError bool 47 CreateTime time.Time 48 DeadlineError bool 49 DeadlineTime time.Time 50 DifferDaysOfDeadline int 51 DiskChargeType string 52 DiskId string 53 DiskName string 54 DiskSize int 55 DiskState string 56 DiskType string 57 DiskUsage string 58 Encrypt bool 59 InstanceId string 60 IsReturnable bool 61 Placement Placement 62 Portable bool 63 RenewFlag string 64 ReturnFailCode int 65 RollbackPercent int 66 Rollbacking bool 67 SnapshotAbility bool 68 DeleteWithInstance bool 69 } 70 71 type SDiskSet []SDisk 72 73 func (v SDiskSet) Len() int { 74 return len(v) 75 } 76 77 func (v SDiskSet) Swap(i, j int) { 78 v[i], v[j] = v[j], v[i] 79 } 80 81 func (v SDiskSet) Less(i, j int) bool { 82 if v[i].DiskUsage == "SYSTEM_DISK" || v[j].DiskUsage == "DATA_DISK" { 83 return true 84 } 85 return false 86 } 87 88 func (self *SRegion) GetDisks(instanceId string, zoneId string, category string, diskIds []string, offset int, limit int) ([]SDisk, int, error) { 89 if limit > 50 || limit <= 0 { 90 limit = 50 91 } 92 params := make(map[string]string) 93 params["Limit"] = fmt.Sprintf("%d", limit) 94 params["Offset"] = fmt.Sprintf("%d", offset) 95 filter := 0 96 97 if len(zoneId) > 0 { 98 params[fmt.Sprintf("Filters.%d.Name", filter)] = "zone" 99 params[fmt.Sprintf("Filters.%d.Values.0", filter)] = zoneId 100 filter++ 101 } 102 103 if len(instanceId) > 0 { 104 params[fmt.Sprintf("Filters.%d.Name", filter)] = "instance-id" 105 params[fmt.Sprintf("Filters.%d.Values.0", filter)] = instanceId 106 filter++ 107 } 108 109 if len(category) > 0 { 110 params[fmt.Sprintf("Filters.%d.Name", filter)] = "disk-type" 111 params[fmt.Sprintf("Filters.%d.Values.0", filter)] = category 112 filter++ 113 } 114 if diskIds != nil && len(diskIds) > 0 { 115 for index, diskId := range diskIds { 116 params[fmt.Sprintf("DiskIds.%d", index)] = diskId 117 } 118 } 119 120 body, err := self.cbsRequest("DescribeDisks", params) 121 if err != nil { 122 log.Errorf("GetDisks fail %s", err) 123 return nil, 0, err 124 } 125 126 disks := make([]SDisk, 0) 127 err = body.Unmarshal(&disks, "DiskSet") 128 if err != nil { 129 log.Errorf("Unmarshal disk details fail %s", err) 130 return nil, 0, err 131 } 132 total, _ := body.Float("TotalCount") 133 sort.Sort(SDiskSet(disks)) 134 return disks, int(total), nil 135 } 136 137 func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { 138 disks, total, err := self.GetDisks("", "", "", []string{diskId}, 0, 1) 139 if err != nil { 140 return nil, err 141 } 142 if total != 1 { 143 return nil, cloudprovider.ErrNotFound 144 } 145 return &disks[0], nil 146 } 147 148 func (self *SDisk) GetId() string { 149 return self.DiskId 150 } 151 152 func (self *SRegion) DeleteDisk(diskId string) error { 153 params := make(map[string]string) 154 params["Region"] = self.Region 155 params["DiskIds.0"] = diskId 156 157 _, err := self.cbsRequest("TerminateDisks", params) 158 return err 159 } 160 161 func (self *SDisk) Delete(ctx context.Context) error { 162 return self.storage.zone.region.DeleteDisk(self.DiskId) 163 } 164 165 func (self *SRegion) ResizeDisk(ctx context.Context, diskId string, sizeGb int64) error { 166 params := make(map[string]string) 167 params["DiskId"] = diskId 168 params["DiskSize"] = fmt.Sprintf("%d", sizeGb) 169 startTime := time.Now() 170 for { 171 _, err := self.cbsRequest("ResizeDisk", params) 172 if err != nil { 173 if strings.Index(err.Error(), "Code=InvalidDisk.Busy") > 0 { 174 log.Infof("The disk is busy, try later ...") 175 time.Sleep(10 * time.Second) 176 if time.Now().Sub(startTime) > time.Minute*20 { 177 return cloudprovider.ErrTimeout 178 } 179 continue 180 } 181 } 182 return err 183 } 184 } 185 186 func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error { 187 return self.storage.zone.region.ResizeDisk(ctx, self.DiskId, sizeMb/1024) 188 } 189 190 func (self *SDisk) GetName() string { 191 if len(self.DiskName) > 0 && self.DiskName != "未命名" { 192 return self.DiskName 193 } 194 return self.DiskId 195 } 196 197 func (self *SDisk) GetGlobalId() string { 198 return self.DiskId 199 } 200 201 func (self *SDisk) IsEmulated() bool { 202 return false 203 } 204 205 func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 206 return self.storage, nil 207 } 208 209 func (self *SDisk) GetStatus() string { 210 switch self.DiskState { 211 case "ATTACHING", "DETACHING", "EXPANDING", "ROLLBACKING": 212 return api.DISK_ALLOCATING 213 default: 214 return api.DISK_READY 215 } 216 } 217 218 func (self *SDisk) Refresh() error { 219 new, err := self.storage.zone.region.GetDisk(self.DiskId) 220 if err != nil { 221 return err 222 } 223 return jsonutils.Update(self, new) 224 } 225 226 func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { 227 snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc) 228 if err != nil { 229 log.Errorf("createSnapshot fail %s", err) 230 return nil, err 231 } 232 snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1) 233 if err != nil { 234 return nil, err 235 } 236 if total == 1 { 237 snapshot := &snapshots[0] 238 err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second) 239 if err != nil { 240 return nil, err 241 } 242 return snapshot, nil 243 } 244 return nil, nil 245 } 246 247 func (self *SDisk) GetDiskType() string { 248 switch self.DiskUsage { 249 case "SYSTEM_DISK": 250 return api.DISK_TYPE_SYS 251 case "DATA_DISK": 252 return api.DISK_TYPE_DATA 253 default: 254 return api.DISK_TYPE_DATA 255 } 256 } 257 258 func (self *SDisk) GetFsFormat() string { 259 return "" 260 } 261 262 func (self *SDisk) GetIsNonPersistent() bool { 263 return false 264 } 265 266 func (self *SDisk) GetDriver() string { 267 return "scsi" 268 } 269 270 func (self *SDisk) GetCacheMode() string { 271 return "none" 272 } 273 274 func (self *SDisk) GetMountpoint() string { 275 return "" 276 } 277 278 func (self *SDisk) GetBillingType() string { 279 switch self.DiskChargeType { 280 case "PREPAID": 281 return billing_api.BILLING_TYPE_PREPAID 282 case "POSTPAID_BY_HOUR": 283 return billing_api.BILLING_TYPE_POSTPAID 284 default: 285 return billing_api.BILLING_TYPE_PREPAID 286 } 287 } 288 289 func (self *SDisk) GetDiskFormat() string { 290 return "vhd" 291 } 292 293 func (self *SDisk) GetDiskSizeMB() int { 294 return self.DiskSize * 1024 295 } 296 297 func (self *SDisk) GetIsAutoDelete() bool { 298 return self.DeleteWithInstance 299 } 300 301 func (self *SDisk) GetCreatedAt() time.Time { 302 // 2019-12-25 09:00:43 #非UTC时间 303 return self.CreateTime.Add(time.Hour * -8) 304 } 305 306 func (self *SDisk) GetExpiredAt() time.Time { 307 if self.DeadlineTime.IsZero() { 308 return time.Time{} 309 } 310 return self.DeadlineTime.Add(time.Hour * -8) 311 } 312 313 func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { 314 snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1) 315 if err != nil { 316 return nil, err 317 } 318 if total == 1 { 319 return &snapshots[0], nil 320 } 321 return nil, cloudprovider.ErrNotFound 322 } 323 324 func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 325 snapshots := make([]SSnapshot, 0) 326 for { 327 parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, 0, 20) 328 if err != nil { 329 log.Errorf("GetDisks fail %s", err) 330 return nil, err 331 } 332 snapshots = append(snapshots, parts...) 333 if len(snapshots) >= total { 334 break 335 } 336 } 337 isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)) 338 for i := 0; i < len(snapshots); i++ { 339 snapshots[i].region = self.storage.zone.region 340 isnapshots[i] = &snapshots[i] 341 } 342 return isnapshots, nil 343 } 344 345 func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { 346 return self.storage.zone.region.GetSnapshotIdByDiskId(self.GetId()) 347 } 348 349 func (self *SDisk) GetTemplateId() string { 350 //return self.ImageId 351 return "" 352 } 353 354 func (self *SRegion) ResetDisk(diskId, snapshotId string) error { 355 params := make(map[string]string) 356 params["Region"] = self.Region 357 params["DiskId"] = diskId 358 params["SnapshotId"] = snapshotId 359 _, err := self.cbsRequest("ApplySnapshot", params) 360 if err != nil { 361 log.Errorf("ResetDisk %s to snapshot %s fail %s", diskId, snapshotId, err) 362 return err 363 } 364 return nil 365 } 366 367 func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 368 return "", self.storage.zone.region.ResetDisk(self.DiskId, snapshotId) 369 } 370 371 func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, desc string, projectId string) (string, error) { 372 params := make(map[string]string) 373 params["Region"] = self.Region 374 params["DiskType"] = category 375 params["DiskChargeType"] = "POSTPAID_BY_HOUR" 376 // [TencentCloudSDKError] Code=InvalidParameter, Message=DiskName: vdisk_stress-testvm-qcloud-1_1560117118026502729, length is 48, out of range [0,20] (e11d6c4007e4), RequestId=a8409994-0357-42e9-b028-e11d6c4007e4 377 if len(name) > 20 { 378 name = name[:20] 379 } 380 params["DiskName"] = name 381 params["Placement.Zone"] = zoneId 382 if len(projectId) > 0 { 383 params["Placement.ProjectId"] = projectId 384 } 385 //params["Encrypted"] = "false" 386 params["DiskSize"] = fmt.Sprintf("%d", sizeGb) 387 params["ClientToken"] = utils.GenRequestId(20) 388 389 body, err := self.cbsRequest("CreateDisks", params) 390 if err != nil { 391 return "", err 392 } 393 diskIDSet := []string{} 394 err = body.Unmarshal(&diskIDSet, "DiskIdSet") 395 if err != nil { 396 return "", err 397 } 398 if len(diskIDSet) < 1 { 399 return "", fmt.Errorf("Create Disk error") 400 } 401 return diskIDSet[0], nil 402 } 403 404 func (disk *SDisk) GetAccessPath() string { 405 return "" 406 } 407 408 func (self *SDisk) Rebuild(ctx context.Context) error { 409 // TODO 410 return cloudprovider.ErrNotSupported 411 } 412 413 func (self *SDisk) GetProjectId() string { 414 return strconv.Itoa(self.Placement.ProjectId) 415 }