yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/openstack/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 openstack 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 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 "yunion.io/x/onecloud/pkg/util/version" 33 ) 34 35 const ( 36 DISK_STATUS_CREATING = "creating" // The volume is being created. 37 38 DISK_STATUS_ATTACHING = "attaching" // The volume is attaching to an instance. 39 DISK_STATUS_DETACHING = "detaching" // The volume is detaching from an instance. 40 DISK_STATUS_EXTENDING = "extending" // The volume is being extended. 41 DISK_STATUS_DELETING = "deleting" // The volume is being deleted. 42 43 DISK_STATUS_RETYPING = "retyping" // The volume is changing type to another volume type. 44 DISK_STATUS_AVAILABLE = "available" // The volume is ready to attach to an instance. 45 DISK_STATUS_RESERVED = "reserved" // The volume is reserved for attaching or shelved. 46 DISK_STATUS_IN_USE = "in-use" // The volume is attached to an instance. 47 DISK_STATUS_MAINTENANCE = "maintenance" // The volume is locked and being migrated. 48 DISK_STATUS_AWAITING_TRANSFER = "awaiting-transfer" // The volume is awaiting for transfer. 49 DISK_STATUS_BACKING_UP = "backing-up" // The volume is being backed up. 50 DISK_STATUS_RESTORING_BACKUP = "restoring-backup" // A backup is being restored to the volume. 51 DISK_STATUS_DOWNLOADING = "downloading" // The volume is downloading an image. 52 DISK_STATUS_UPLOADING = "uploading" // The volume is being uploaded to an image. 53 54 DISK_STATUS_ERROR = "error" // A volume creation error occurred. 55 DISK_STATUS_ERROR_DELETING = "error_deleting" // A volume deletion error occurred. 56 DISK_STATUS_ERROR_BACKING_UP = "error_backing-up" // A backup error occurred. 57 DISK_STATUS_ERROR_RESTORING = "error_restoring" // A backup restoration error occurred. 58 DISK_STATUS_ERROR_EXTENDING = "error_extending" // An error occurred while attempting to extend a volume. 59 60 ) 61 62 type Attachment struct { 63 ServerId string 64 AttachmentId string 65 HostName string 66 VolumeId string 67 Device string 68 Id string 69 } 70 71 type Link struct { 72 Href string 73 Rel string 74 } 75 76 type VolumeImageMetadata struct { 77 Checksum string 78 MinRAM int 79 DiskFormat string 80 ImageName string 81 ImageId string 82 ContainerFormat string 83 MinDisk int 84 Size int 85 } 86 87 type SDisk struct { 88 storage *SStorage 89 multicloud.SDisk 90 OpenStackTags 91 92 Id string 93 Name string 94 95 MigrationStatus string 96 Attachments []Attachment 97 Links []Link 98 99 AvailabilityZone string 100 Host string `json:"os-vol-host-attr:host"` 101 Encrypted bool 102 ReplicationStatus string 103 SnapshotId string 104 Size int 105 UserId string 106 TenantId string `json:"os-vol-tenant-attr:tenant_id"` 107 Migstat string `json:"os-vol-mig-status-attr:migstat"` 108 109 Status string 110 Description string 111 Multiattach string 112 SourceVolid string 113 ConsistencygroupId string 114 VolumeImageMetadata VolumeImageMetadata 115 NameId string `json:"os-vol-mig-status-attr:name_id"` 116 Bootable bool 117 CreatedAt time.Time 118 VolumeType string 119 } 120 121 func (region *SRegion) GetDisks() ([]SDisk, error) { 122 disks := []SDisk{} 123 resource := "/volumes/detail" 124 query := url.Values{} 125 query.Set("all_tenants", "true") 126 for { 127 resp, err := region.bsList(resource, query) 128 if err != nil { 129 return nil, errors.Wrap(err, "bsList") 130 } 131 part := struct { 132 Volumes []SDisk 133 VolumesLinks SNextLinks 134 }{} 135 err = resp.Unmarshal(&part) 136 if err != nil { 137 return nil, errors.Wrap(err, "resp.Unmarshal") 138 } 139 disks = append(disks, part.Volumes...) 140 marker := part.VolumesLinks.GetNextMark() 141 if len(marker) == 0 { 142 break 143 } 144 query.Set("marker", marker) 145 } 146 return disks, nil 147 } 148 149 func (disk *SDisk) GetId() string { 150 return disk.Id 151 } 152 153 func (disk *SDisk) Delete(ctx context.Context) error { 154 err := disk.storage.zone.region.DeleteDisk(disk.Id) 155 if err != nil { 156 return err 157 } 158 return cloudprovider.WaitDeleted(disk, 10*time.Second, 8*time.Minute) 159 } 160 161 func (disk *SDisk) attachInstances(attachments []Attachment) error { 162 for _, attachment := range attachments { 163 startTime := time.Now() 164 for time.Now().Sub(startTime) < 5*time.Minute { 165 if err := disk.storage.zone.region.AttachDisk(attachment.ServerId, disk.Id); err != nil { 166 if strings.Contains(err.Error(), "status must be available or downloading") { 167 time.Sleep(time.Second * 10) 168 continue 169 } 170 log.Errorf("recover attach disk %s => instance %s error: %v", disk.Id, attachment.ServerId, err) 171 return err 172 } else { 173 return nil 174 } 175 } 176 } 177 return nil 178 } 179 180 func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error { 181 maxVersion := "" 182 for _, service := range []string{OPENSTACK_SERVICE_VOLUMEV3, OPENSTACK_SERVICE_VOLUMEV2, OPENSTACK_SERVICE_VOLUME} { 183 maxVersion, _ = disk.storage.zone.region.GetMaxVersion(service) 184 if len(maxVersion) > 0 { 185 break 186 } 187 } 188 if version.GE(maxVersion, "3.42") { 189 return disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb) 190 } 191 instanceIds := []string{} 192 for _, attachement := range disk.Attachments { 193 if err := disk.storage.zone.region.DetachDisk(attachement.ServerId, disk.Id); err != nil { 194 return err 195 } 196 instanceIds = append(instanceIds, attachement.ServerId) 197 } 198 err := disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb) 199 if err != nil { 200 disk.attachInstances(disk.Attachments) 201 return err 202 } 203 return disk.attachInstances(disk.Attachments) 204 } 205 206 func (disk *SDisk) GetName() string { 207 if len(disk.Name) > 0 { 208 return disk.Name 209 } 210 return disk.Id 211 } 212 213 func (disk *SDisk) GetGlobalId() string { 214 return disk.Id 215 } 216 217 func (disk *SDisk) IsEmulated() bool { 218 return false 219 } 220 221 func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 222 return disk.storage, nil 223 } 224 225 func (disk *SDisk) GetStatus() string { 226 switch disk.Status { 227 case DISK_STATUS_CREATING, DISK_STATUS_DOWNLOADING: 228 return api.DISK_ALLOCATING 229 case DISK_STATUS_ATTACHING: 230 return api.DISK_ATTACHING 231 case DISK_STATUS_DETACHING: 232 return api.DISK_DETACHING 233 case DISK_STATUS_EXTENDING: 234 return api.DISK_RESIZING 235 case DISK_STATUS_RETYPING, DISK_STATUS_AVAILABLE, DISK_STATUS_RESERVED, DISK_STATUS_IN_USE, DISK_STATUS_MAINTENANCE, DISK_STATUS_AWAITING_TRANSFER, DISK_STATUS_BACKING_UP, DISK_STATUS_RESTORING_BACKUP, DISK_STATUS_UPLOADING: 236 return api.DISK_READY 237 case DISK_STATUS_DELETING: 238 return api.DISK_DEALLOC 239 case DISK_STATUS_ERROR: 240 return api.DISK_ALLOC_FAILED 241 default: 242 return api.DISK_UNKNOWN 243 } 244 } 245 246 func (disk *SDisk) Refresh() error { 247 _disk, err := disk.storage.zone.region.GetDisk(disk.Id) 248 if err != nil { 249 return err 250 } 251 return jsonutils.Update(disk, _disk) 252 } 253 254 func (disk *SDisk) ResizeDisk(sizeMb int64) error { 255 return disk.storage.zone.region.ResizeDisk(disk.Id, sizeMb) 256 } 257 258 func (disk *SDisk) GetDiskFormat() string { 259 return "lvm" 260 } 261 262 func (disk *SDisk) GetDiskSizeMB() int { 263 return disk.Size * 1024 264 } 265 266 func (disk *SDisk) GetIsAutoDelete() bool { 267 return false 268 } 269 270 func (disk *SDisk) GetTemplateId() string { 271 return disk.VolumeImageMetadata.ImageId 272 } 273 274 func (disk *SDisk) GetDiskType() string { 275 if disk.Bootable { 276 return api.DISK_TYPE_SYS 277 } 278 return api.DISK_TYPE_DATA 279 } 280 281 func (disk *SDisk) GetFsFormat() string { 282 return "" 283 } 284 285 func (disk *SDisk) GetIsNonPersistent() bool { 286 return false 287 } 288 289 func (disk *SDisk) GetDriver() string { 290 return "scsi" 291 } 292 293 func (disk *SDisk) GetCacheMode() string { 294 return "none" 295 } 296 297 func (disk *SDisk) GetMountpoint() string { 298 return "" 299 } 300 301 func (region *SRegion) CreateDisk(imageRef string, volumeType string, name string, sizeGb int, desc string, projectId string) (*SDisk, error) { 302 params := map[string]map[string]interface{}{ 303 "volume": { 304 "size": sizeGb, 305 "volume_type": volumeType, 306 "name": name, 307 "description": desc, 308 }, 309 } 310 if len(imageRef) > 0 { 311 params["volume"]["imageRef"] = imageRef 312 } 313 resp, err := region.bsCreate(projectId, "/volumes", params) 314 if err != nil { 315 return nil, errors.Wrap(err, "bsCreate") 316 } 317 318 disk := &SDisk{} 319 err = resp.Unmarshal(disk, "volume") 320 if err != nil { 321 return nil, errors.Wrap(err, "resp.Unmarshal") 322 } 323 //这里由于不好初始化disk的storage就手动循环了,如果是通过镜像创建,有个下载过程,比较慢,等待时间较长 324 startTime := time.Now() 325 timeout := time.Minute * 10 326 //若是通过镜像创建,需要先下载镜像,需要的时间更长 327 if len(imageRef) > 0 { 328 timeout = time.Minute * 30 329 } 330 for time.Now().Sub(startTime) < timeout { 331 disk, err = region.GetDisk(disk.GetGlobalId()) 332 if err != nil { 333 return nil, errors.Wrapf(err, "GetDisk(%s)", disk.GetGlobalId()) 334 } 335 log.Debugf("disk status %s expect %s", disk.GetStatus(), api.DISK_READY) 336 status := disk.GetStatus() 337 if status == api.DISK_READY { 338 break 339 } 340 if status == api.DISK_ALLOC_FAILED { 341 messages, _ := region.GetMessages(disk.Id) 342 if len(messages) > 0 { 343 return nil, fmt.Errorf("allocate disk %s failed, status is %s message: %s", disk.Name, disk.Status, messages[0].UserMessage) 344 } 345 return nil, fmt.Errorf("allocate disk %s failed, status is %s", disk.Name, disk.Status) 346 } 347 time.Sleep(time.Second * 10) 348 } 349 if disk.GetStatus() != api.DISK_READY { 350 return nil, fmt.Errorf("timeout for waitting disk ready, current status: %s", disk.Status) 351 } 352 return disk, nil 353 } 354 355 func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { 356 resource := fmt.Sprintf("/volumes/%s", diskId) 357 resp, err := region.bsGet(resource) 358 if err != nil { 359 return nil, errors.Wrap(err, "bsGet") 360 } 361 disk := &SDisk{} 362 err = resp.Unmarshal(disk, "volume") 363 if err != nil { 364 return nil, errors.Wrap(err, "resp.Unmarshal") 365 } 366 return disk, nil 367 } 368 369 func (region *SRegion) DeleteDisk(diskId string) error { 370 resource := fmt.Sprintf("/volumes/%s", diskId) 371 _, err := region.bsDelete(resource) 372 return err 373 } 374 375 func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error { 376 params := map[string]map[string]interface{}{ 377 "os-extend": { 378 "new_size": sizeMb / 1024, 379 }, 380 } 381 resource := fmt.Sprintf("/volumes/%s/action", diskId) 382 _, err := region.bsPost(resource, params) 383 return err 384 } 385 386 func (region *SRegion) ResetDisk(diskId, snapshotId string) error { 387 params := map[string]map[string]interface{}{ 388 "revert": { 389 "snapshot_id": snapshotId, 390 }, 391 } 392 resource := fmt.Sprintf("/volumes/%s/action", diskId) 393 _, err := region.bsPost(resource, params) 394 return err 395 } 396 397 func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { 398 snapshot, err := disk.storage.zone.region.CreateSnapshot(disk.Id, name, desc) 399 if err != nil { 400 return nil, err 401 } 402 return snapshot, cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, time.Second*5, time.Minute*5) 403 } 404 405 func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { 406 return disk.storage.zone.region.GetISnapshotById(snapshotId) 407 } 408 409 func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 410 snapshots, err := disk.storage.zone.region.GetSnapshots(disk.Id) 411 if err != nil { 412 return nil, errors.Wrapf(err, "GetSnapshots(%s)", disk.Id) 413 } 414 isnapshots := []cloudprovider.ICloudSnapshot{} 415 for i := range snapshots { 416 snapshots[i].region = disk.storage.zone.region 417 isnapshots = append(isnapshots, &snapshots[i]) 418 } 419 return isnapshots, nil 420 } 421 422 func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 423 return disk.Id, disk.storage.zone.region.ResetDisk(disk.Id, snapshotId) 424 } 425 426 func (disk *SDisk) GetBillingType() string { 427 return billing_api.BILLING_TYPE_POSTPAID 428 } 429 430 func (disk *SDisk) GetCreatedAt() time.Time { 431 return disk.CreatedAt 432 } 433 434 func (disk *SDisk) GetExpiredAt() time.Time { 435 return time.Time{} 436 } 437 438 func (disk *SDisk) GetAccessPath() string { 439 return "" 440 } 441 442 func (disk *SDisk) Rebuild(ctx context.Context) error { 443 return cloudprovider.ErrNotSupported 444 } 445 446 func (disk *SDisk) GetProjectId() string { 447 return disk.TenantId 448 }