disk.go 11 KB

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