yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/zstack/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 zstack 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 27 api "yunion.io/x/cloudmux/pkg/apis/compute" 28 "yunion.io/x/cloudmux/pkg/cloudprovider" 29 "yunion.io/x/cloudmux/pkg/multicloud" 30 ) 31 32 type SDisk struct { 33 multicloud.SDisk 34 ZStackTags 35 36 localStorage *SLocalStorage 37 storage *SStorage 38 region *SRegion 39 40 ZStackBasic 41 PrimaryStorageUUID string `json:"primaryStorageUuid"` 42 VMInstanceUUID string `json:"vmInstanceUuid"` 43 DiskOfferingUUID string `json:"diskOfferingUuid"` 44 RootImageUUID string `json:"rootImageUuid"` 45 InstallPath string `json:"installPath"` 46 Type string `json:"type"` 47 Format string `json:"format"` 48 Size int `json:"size"` 49 ActualSize int `json:"actualSize"` 50 DeviceID float32 `json:"deviceId"` 51 State string `json:"state"` 52 Status string `json:"status"` 53 54 ZStackTime 55 } 56 57 func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { 58 disk := &SDisk{region: region} 59 err := region.client.getResource("volumes", diskId, disk) 60 if err != nil { 61 return nil, err 62 } 63 if disk.Status == "NotInstantiated" || disk.Status == "Deleted" { 64 return nil, cloudprovider.ErrNotFound 65 } 66 return disk, nil 67 } 68 69 func (region *SRegion) GetDiskWithStorage(diskId string) (*SDisk, error) { 70 disk, err := region.GetDisk(diskId) 71 if err != nil { 72 log.Errorf("Get Disk %s error: %v", diskId, err) 73 return nil, err 74 } 75 storage, err := region.GetStorage(disk.PrimaryStorageUUID) 76 if err != nil { 77 log.Errorf("Get primary storage %s error: %v", disk.PrimaryStorageUUID, err) 78 return nil, err 79 } 80 switch storage.Type { 81 case StorageTypeLocal: 82 if len(disk.VMInstanceUUID) > 0 { 83 instance, err := region.GetInstance(disk.VMInstanceUUID) 84 if err != nil { 85 return nil, err 86 } 87 hostId := instance.LastHostUUID 88 if len(hostId) == 0 { 89 hostId = instance.HostUUID 90 } 91 disk.localStorage = &SLocalStorage{region: region, primaryStorageID: storage.UUID, HostUUID: hostId} 92 return disk, nil 93 } 94 tags, err := region.GetResourceSysTags("", "VolumeVO", disk.UUID, "") 95 if err != nil { 96 log.Errorf("get disk tag error: %v", err) 97 return nil, err 98 } 99 for i := 0; i < len(tags); i++ { 100 if strings.HasPrefix(tags[i].Tag, "localStorage::hostUuid::") { 101 hostInfo := strings.Split(tags[i].Tag, "localStorage::hostUuid::") 102 if len(hostInfo) == 2 { 103 localStorage, err := region.GetLocalStorage(storage.UUID, hostInfo[1]) 104 if err != nil { 105 return nil, err 106 } 107 disk.localStorage = localStorage 108 return disk, nil 109 } 110 return nil, fmt.Errorf("invalid host info %s from disk %s", tags[i].Tag, disk.Name) 111 } 112 } 113 return nil, cloudprovider.ErrNotFound 114 case StorageTypeCeph: 115 disk.storage = storage 116 return disk, nil 117 default: 118 return nil, fmt.Errorf("Unsupport StorageType %s", storage.Type) 119 } 120 } 121 122 func (region *SRegion) GetDisks(storageId string, diskIds []string, diskType string) ([]SDisk, error) { 123 disks := []SDisk{} 124 params := url.Values{} 125 params.Add("q", "status!=Deleted") 126 params.Add("q", "status!=NotInstantiated") 127 if len(storageId) > 0 { 128 params.Add("q", "primaryStorageUuid="+storageId) 129 } 130 if len(diskIds) > 0 { 131 params.Add("q", "uuid?="+strings.Join(diskIds, ",")) 132 } 133 if len(diskType) > 0 { 134 params.Add("q", "type="+diskType) 135 } 136 return disks, region.client.listAll("volumes", params, &disks) 137 } 138 139 func (disk *SDisk) GetSysTags() map[string]string { 140 data := map[string]string{} 141 data["hypervisor"] = api.HYPERVISOR_ZSTACK 142 return data 143 } 144 145 func (disk *SDisk) GetId() string { 146 return disk.UUID 147 } 148 149 func (disk *SDisk) Delete(ctx context.Context) error { 150 if disk.Status == "Deleted" { 151 return disk.region.ExpungeDisk(disk.UUID) 152 } 153 return disk.region.DeleteDisk(disk.UUID) 154 } 155 156 func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error { 157 return disk.region.ResizeDisk(disk.UUID, sizeMb) 158 } 159 160 func (disk *SDisk) GetName() string { 161 return disk.Name 162 } 163 164 func (disk *SDisk) GetGlobalId() string { 165 return disk.UUID 166 } 167 168 func (disk *SDisk) IsEmulated() bool { 169 return false 170 } 171 172 func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { 173 if disk.localStorage != nil { 174 return disk.localStorage, nil 175 } 176 if disk.storage != nil { 177 return disk.storage, nil 178 } 179 return nil, cloudprovider.ErrNotFound 180 } 181 182 func (disk *SDisk) GetIStorageId() string { 183 storage, err := disk.region.GetStorage(disk.PrimaryStorageUUID) 184 if err != nil { 185 return disk.PrimaryStorageUUID 186 } else if storage.Type == StorageTypeLocal && len(disk.VMInstanceUUID) > 0 { 187 instnace, err := disk.region.GetInstance(disk.VMInstanceUUID) 188 if err != nil { 189 log.Warningf("failed to get instance %s for disk %s(%s) error: %v", disk.VMInstanceUUID, disk.Name, disk.UUID, err) 190 return "" 191 } 192 return fmt.Sprintf("%s/%s", disk.PrimaryStorageUUID, instnace.LastHostUUID) 193 } 194 return disk.PrimaryStorageUUID 195 } 196 197 func (disk *SDisk) GetStatus() string { 198 switch disk.Status { 199 case "Ready": 200 return api.DISK_READY 201 case "NotInstantiated": 202 //数据云盘特有的状态。在这个连接状态中,数据云盘只存在于数据库的表记录中。NotInstantiated状态的数据云盘可以挂载到任何类型虚拟机管理程序管理的云主机上;当挂载到云主机上后,数据云盘的hypervisorType域会存储云主机对应的虚拟机管理程序类型,在主存储上被实例化为虚拟机管理程序类型的实际二进制文件,同时连接状态会改为就绪(Ready);在这之后,这些数据云盘就只能被重新挂载到相同类型虚拟机管理程序管理的云主机上了。 203 return api.DISK_INIT 204 case "Creating": 205 return api.DISK_ALLOCATING 206 case "Deleted": 207 return api.DISK_DEALLOC 208 default: 209 log.Errorf("Unknown disk %s(%s) status %s", disk.Name, disk.UUID, disk.Status) 210 return api.DISK_UNKNOWN 211 } 212 } 213 214 func (disk *SDisk) Refresh() error { 215 new, err := disk.region.GetDisk(disk.UUID) 216 if err != nil { 217 return err 218 } 219 return jsonutils.Update(disk, new) 220 } 221 222 func (disk *SDisk) GetDiskFormat() string { 223 return disk.Format 224 } 225 226 func (disk *SDisk) GetDiskSizeMB() int { 227 return disk.Size / 1024 / 1024 228 } 229 230 func (disk *SDisk) GetIsAutoDelete() bool { 231 return disk.GetDiskType() == api.DISK_TYPE_SYS || disk.localStorage != nil 232 } 233 234 func (disk *SDisk) GetTemplateId() string { 235 return disk.RootImageUUID 236 } 237 238 func (disk *SDisk) GetDiskType() string { 239 switch disk.Type { 240 case "Root": 241 return api.DISK_TYPE_SYS 242 default: 243 return api.DISK_TYPE_DATA 244 } 245 } 246 247 func (disk *SDisk) GetFsFormat() string { 248 return "" 249 } 250 251 func (disk *SDisk) GetIsNonPersistent() bool { 252 return false 253 } 254 255 func (disk *SDisk) GetDriver() string { 256 return "scsi" 257 } 258 259 func (disk *SDisk) GetCacheMode() string { 260 return "none" 261 } 262 263 func (disk *SDisk) GetMountpoint() string { 264 return "" 265 } 266 267 func (region *SRegion) CreateDisk(name string, storageId string, hostId string, poolName string, sizeGb int, desc string) (*SDisk, error) { 268 offerings, err := region.GetDiskOfferings(sizeGb) 269 if err != nil { 270 return nil, err 271 } 272 diskOfferingUuid := "" 273 if len(offerings) > 0 { 274 diskOfferingUuid = offerings[0].UUID 275 } else { 276 offering, err := region.CreateDiskOffering(sizeGb) 277 if err != nil { 278 return nil, err 279 } 280 diskOfferingUuid = offering.UUID 281 defer region.DeleteDiskOffering(diskOfferingUuid) 282 } 283 params := map[string]interface{}{ 284 "params": map[string]string{ 285 "name": name, 286 "description": desc, 287 "diskOfferingUuid": diskOfferingUuid, 288 "primaryStorageUuid": storageId, 289 }, 290 } 291 if len(hostId) > 0 { 292 params["systemTags"] = []string{"localStorage::hostUuid::" + hostId} 293 } 294 if len(poolName) > 0 { 295 params["systemTags"] = []string{"ceph::pool::" + poolName} 296 } 297 resp, err := region.client.post("volumes/data", jsonutils.Marshal(params)) 298 if err != nil { 299 return nil, err 300 } 301 disk := &SDisk{region: region} 302 return disk, resp.Unmarshal(disk, "inventory") 303 } 304 305 func (region *SRegion) ExpungeDisk(diskId string) error { 306 params := map[string]interface{}{ 307 "expungeDataVolume": jsonutils.NewDict(), 308 } 309 _, err := region.client.put("volumes", diskId, jsonutils.Marshal(params)) 310 return err 311 } 312 313 func (region *SRegion) DeleteDisk(diskId string) error { 314 err := region.client.delete("volumes", diskId, "Enforcing") 315 if err != nil { 316 return err 317 } 318 return region.ExpungeDisk(diskId) 319 } 320 321 func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error { 322 disk, err := region.GetDisk(diskId) 323 if err != nil { 324 return err 325 } 326 327 if disk.GetDiskSizeMB() == int(sizeMb) { 328 return nil 329 } 330 331 params := jsonutils.Marshal(map[string]interface{}{ 332 fmt.Sprintf("resize%sVolume", disk.Type): map[string]int64{ 333 "size": sizeMb * 1024 * 1024, 334 }, 335 }) 336 resource := "volumes/resize" 337 if disk.Type == "Data" { 338 resource = "volumes/data/resize" 339 } 340 _, err = region.client.put(resource, diskId, params) 341 return err 342 } 343 344 func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { 345 return disk.region.CreateSnapshot(name, disk.UUID, desc) 346 } 347 348 func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { 349 snapshots, err := disk.region.GetSnapshots(snapshotId, disk.UUID) 350 if err != nil { 351 return nil, err 352 } 353 if len(snapshots) == 1 { 354 if snapshots[0].UUID == snapshotId { 355 return &snapshots[0], nil 356 } 357 return nil, cloudprovider.ErrNotFound 358 } 359 if len(snapshots) > 1 { 360 return nil, cloudprovider.ErrDuplicateId 361 } 362 return nil, cloudprovider.ErrNotFound 363 } 364 365 func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { 366 snapshots, err := disk.region.GetSnapshots("", disk.UUID) 367 if err != nil { 368 return nil, err 369 } 370 isnapshots := []cloudprovider.ICloudSnapshot{} 371 for i := 0; i < len(snapshots); i++ { 372 isnapshots = append(isnapshots, &snapshots[i]) 373 } 374 return isnapshots, nil 375 } 376 377 func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { 378 _, err := disk.region.ResetDisks(snapshotId) 379 return disk.UUID, err 380 } 381 382 func (region *SRegion) ResetDisks(snapshotId string) (jsonutils.JSONObject, error) { 383 params := map[string]interface{}{ 384 "revertVolumeFromSnapshot": jsonutils.NewDict(), 385 } 386 return region.client.put("volume-snapshots", snapshotId, jsonutils.Marshal(params)) 387 } 388 389 func (disk *SDisk) GetBillingType() string { 390 return "" 391 } 392 393 func (disk *SDisk) GetExpiredAt() time.Time { 394 return time.Time{} 395 } 396 397 func (disk *SDisk) GetCreatedAt() time.Time { 398 return disk.CreateDate 399 } 400 401 func (disk *SDisk) GetAccessPath() string { 402 return disk.InstallPath 403 } 404 405 func (disk *SDisk) Rebuild(ctx context.Context) error { 406 return disk.region.RebuildDisk(disk.UUID) 407 } 408 409 func (region *SRegion) RebuildDisk(diskId string) error { 410 params := map[string]interface{}{ 411 "recoverDataVolume": jsonutils.NewDict(), 412 } 413 _, err := region.client.put("volumes", diskId, jsonutils.Marshal(params)) 414 return err 415 } 416 417 func (disk *SDisk) GetProjectId() string { 418 return "" 419 }