disk.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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 hcso
  15. import (
  16. "context"
  17. "strings"
  18. "time"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  23. api "yunion.io/x/cloudmux/pkg/apis/compute"
  24. "yunion.io/x/cloudmux/pkg/cloudprovider"
  25. "yunion.io/x/cloudmux/pkg/multicloud"
  26. "yunion.io/x/cloudmux/pkg/multicloud/huawei"
  27. )
  28. /*
  29. 华为云云硬盘
  30. ======创建==========
  31. 1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区
  32. 2.计费模式 包年包月/按需计费
  33. 3.*支持自动备份
  34. 共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html
  35. 根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。
  36. 一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。
  37. 单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。
  38. */
  39. type Attachment struct {
  40. ServerID string `json:"server_id"`
  41. AttachmentID string `json:"attachment_id"`
  42. AttachedAt string `json:"attached_at"`
  43. HostName string `json:"host_name"`
  44. VolumeID string `json:"volume_id"`
  45. Device string `json:"device"`
  46. ID string `json:"id"`
  47. }
  48. type DiskMeta struct {
  49. ResourceSpecCode string `json:"resourceSpecCode"`
  50. Billing string `json:"billing"`
  51. ResourceType string `json:"resourceType"`
  52. AttachedMode string `json:"attached_mode"`
  53. Readonly string `json:"readonly"`
  54. }
  55. type VolumeImageMetadata struct {
  56. QuickStart string `json:"__quick_start"`
  57. ContainerFormat string `json:"container_format"`
  58. MinRAM string `json:"min_ram"`
  59. ImageName string `json:"image_name"`
  60. ImageID string `json:"image_id"`
  61. OSType string `json:"__os_type"`
  62. OSFeatureList string `json:"__os_feature_list"`
  63. MinDisk string `json:"min_disk"`
  64. SupportKVM string `json:"__support_kvm"`
  65. VirtualEnvType string `json:"virtual_env_type"`
  66. SizeGB string `json:"size"`
  67. OSVersion string `json:"__os_version"`
  68. OSBit string `json:"__os_bit"`
  69. SupportKVMHi1822Hiovs string `json:"__support_kvm_hi1822_hiovs"`
  70. SupportXen string `json:"__support_xen"`
  71. Description string `json:"__description"`
  72. Imagetype string `json:"__imagetype"`
  73. DiskFormat string `json:"disk_format"`
  74. ImageSourceType string `json:"__image_source_type"`
  75. Checksum string `json:"checksum"`
  76. Isregistered string `json:"__isregistered"`
  77. HwVifMultiqueueEnabled string `json:"hw_vif_multiqueue_enabled"`
  78. Platform string `json:"__platform"`
  79. }
  80. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html
  81. type SDisk struct {
  82. storage *SStorage
  83. multicloud.SDisk
  84. huawei.HuaweiDiskTags
  85. details *SResourceDetail
  86. ID string `json:"id"`
  87. Name string `json:"name"`
  88. Status string `json:"status"`
  89. Attachments []Attachment `json:"attachments"`
  90. Description string `json:"description"`
  91. SizeGB int `json:"size"`
  92. Metadata DiskMeta `json:"metadata"`
  93. Encrypted bool `json:"encrypted"`
  94. Bootable string `json:"bootable"`
  95. Multiattach bool `json:"multiattach"`
  96. AvailabilityZone string `json:"availability_zone"`
  97. SourceVolid string `json:"source_volid"`
  98. SnapshotID string `json:"snapshot_id"`
  99. CreatedAt time.Time `json:"created_at"`
  100. VolumeType string `json:"volume_type"`
  101. VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"`
  102. ReplicationStatus string `json:"replication_status"`
  103. UserID string `json:"user_id"`
  104. ConsistencygroupID string `json:"consistencygroup_id"`
  105. UpdatedAt string `json:"updated_at"`
  106. EnterpriseProjectId string
  107. ExpiredTime time.Time
  108. }
  109. func (self *SDisk) GetId() string {
  110. return self.ID
  111. }
  112. func (self *SDisk) GetName() string {
  113. if len(self.Name) == 0 {
  114. return self.ID
  115. }
  116. return self.Name
  117. }
  118. func (self *SDisk) GetGlobalId() string {
  119. return self.ID
  120. }
  121. func (self *SDisk) GetStatus() string {
  122. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html
  123. switch self.Status {
  124. case "creating", "downloading":
  125. return api.DISK_ALLOCATING
  126. case "available", "in-use":
  127. return api.DISK_READY
  128. case "error":
  129. return api.DISK_ALLOC_FAILED
  130. case "attaching":
  131. return api.DISK_ATTACHING
  132. case "detaching":
  133. return api.DISK_DETACHING
  134. case "restoring-backup":
  135. return api.DISK_REBUILD
  136. case "backing-up":
  137. return api.DISK_BACKUP_STARTALLOC // ?
  138. case "error_restoring":
  139. return api.DISK_BACKUP_ALLOC_FAILED
  140. case "uploading":
  141. return api.DISK_SAVING //?
  142. case "extending":
  143. return api.DISK_RESIZING
  144. case "error_extending":
  145. return api.DISK_ALLOC_FAILED // ?
  146. case "deleting":
  147. return api.DISK_DEALLOC //?
  148. case "error_deleting":
  149. return api.DISK_DEALLOC_FAILED // ?
  150. case "rollbacking":
  151. return api.DISK_REBUILD
  152. case "error_rollbacking":
  153. return api.DISK_UNKNOWN
  154. default:
  155. return api.DISK_UNKNOWN
  156. }
  157. }
  158. func (self *SDisk) Refresh() error {
  159. new, err := self.storage.zone.region.GetDisk(self.GetId())
  160. if err != nil {
  161. return err
  162. }
  163. return jsonutils.Update(self, new)
  164. }
  165. func (self *SDisk) IsEmulated() bool {
  166. return false
  167. }
  168. func (self *SDisk) GetBillingType() string {
  169. return billing_api.BILLING_TYPE_POSTPAID
  170. }
  171. func (self *SDisk) GetCreatedAt() time.Time {
  172. return self.CreatedAt
  173. }
  174. func (self *SDisk) GetExpiredAt() time.Time {
  175. var expiredTime time.Time
  176. return expiredTime
  177. }
  178. func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
  179. return self.storage, nil
  180. }
  181. func (self *SDisk) GetDiskFormat() string {
  182. // self.volume_type ?
  183. return "vhd"
  184. }
  185. func (self *SDisk) GetDiskSizeMB() int {
  186. return int(self.SizeGB * 1024)
  187. }
  188. func (self *SDisk) checkAutoDelete(attachments []Attachment) bool {
  189. autodelete := false
  190. for _, attach := range attachments {
  191. if len(attach.ServerID) > 0 {
  192. // todo : 忽略错误??
  193. vm, err := self.storage.zone.region.GetInstanceByID(attach.ServerID)
  194. if err != nil {
  195. volumes := vm.OSExtendedVolumesVolumesAttached
  196. for _, vol := range volumes {
  197. if vol.ID == self.ID && strings.ToLower(vol.DeleteOnTermination) == "true" {
  198. autodelete = true
  199. }
  200. }
  201. }
  202. break
  203. }
  204. }
  205. return autodelete
  206. }
  207. func (self *SDisk) GetIsAutoDelete() bool {
  208. if len(self.Attachments) > 0 {
  209. return self.checkAutoDelete(self.Attachments)
  210. }
  211. return false
  212. }
  213. func (self *SDisk) GetTemplateId() string {
  214. return self.VolumeImageMetadata.ImageID
  215. }
  216. // Bootable 表示硬盘是否为启动盘。
  217. // 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
  218. func (self *SDisk) GetDiskType() string {
  219. if self.Bootable == "true" {
  220. return api.DISK_TYPE_SYS
  221. } else {
  222. return api.DISK_TYPE_DATA
  223. }
  224. }
  225. func (self *SDisk) GetFsFormat() string {
  226. return ""
  227. }
  228. func (self *SDisk) GetIsNonPersistent() bool {
  229. return false
  230. }
  231. func (self *SDisk) GetDriver() string {
  232. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762431.html
  233. // scsi or vbd?
  234. // todo: implement me
  235. return "scsi"
  236. }
  237. func (self *SDisk) GetCacheMode() string {
  238. return "none"
  239. }
  240. func (self *SDisk) GetMountpoint() string {
  241. if len(self.Attachments) > 0 {
  242. return self.Attachments[0].Device
  243. }
  244. return ""
  245. }
  246. func (self *SDisk) GetMountServerId() string {
  247. if len(self.Attachments) > 0 {
  248. return self.Attachments[0].ServerID
  249. }
  250. return ""
  251. }
  252. func (self *SDisk) GetAccessPath() string {
  253. return ""
  254. }
  255. func (self *SDisk) Delete(ctx context.Context) error {
  256. disk, err := self.storage.zone.region.GetDisk(self.GetId())
  257. if err != nil {
  258. if errors.Cause(err) == cloudprovider.ErrNotFound {
  259. return nil
  260. }
  261. return err
  262. }
  263. if disk.Status != "deleting" {
  264. // 等待硬盘ready
  265. cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second)
  266. err := self.storage.zone.region.DeleteDisk(self.GetId())
  267. if err != nil {
  268. return err
  269. }
  270. }
  271. return cloudprovider.WaitDeleted(self, 10*time.Second, 120*time.Second)
  272. }
  273. func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
  274. if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc); err != nil {
  275. log.Errorf("createSnapshot fail %s", err)
  276. return nil, err
  277. } else if snapshot, err := self.getSnapshot(snapshotId); err != nil {
  278. return nil, err
  279. } else {
  280. snapshot.region = self.storage.zone.region
  281. if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil {
  282. return nil, err
  283. }
  284. return snapshot, nil
  285. }
  286. }
  287. func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) {
  288. snapshot, err := self.storage.zone.region.GetSnapshotById(snapshotId)
  289. return &snapshot, err
  290. }
  291. func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
  292. snapshot, err := self.getSnapshot(snapshotId)
  293. return snapshot, err
  294. }
  295. func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
  296. snapshots, err := self.storage.zone.region.GetSnapshots(self.ID, "")
  297. if err != nil {
  298. return nil, err
  299. }
  300. isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots))
  301. for i := 0; i < len(snapshots); i++ {
  302. isnapshots[i] = &snapshots[i]
  303. }
  304. return isnapshots, nil
  305. }
  306. func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
  307. err := cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second)
  308. if err != nil {
  309. return err
  310. }
  311. sizeGb := newSizeMB / 1024
  312. err = self.storage.zone.region.resizeDisk(self.GetId(), sizeGb)
  313. if err != nil {
  314. return err
  315. }
  316. return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 15*time.Second, 5*time.Second, 60*time.Second)
  317. }
  318. func (self *SDisk) Detach() error {
  319. err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId())
  320. if err != nil {
  321. log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
  322. return err
  323. }
  324. return cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool {
  325. err := self.Refresh()
  326. if err != nil {
  327. log.Debugln(err)
  328. return false
  329. }
  330. if self.Status == "available" {
  331. return true
  332. }
  333. return false
  334. })
  335. }
  336. func (self *SDisk) Attach(device string) error {
  337. err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device)
  338. if err != nil {
  339. log.Debugf("attach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
  340. return err
  341. }
  342. return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 10*time.Second, 5*time.Second, 60*time.Second)
  343. }
  344. // 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html
  345. // 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载
  346. func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
  347. mountpoint := self.GetMountpoint()
  348. if len(mountpoint) > 0 {
  349. err := self.Detach()
  350. if err != nil {
  351. return "", err
  352. }
  353. }
  354. diskId, err := self.storage.zone.region.resetDisk(self.GetId(), snapshotId)
  355. if err != nil {
  356. return diskId, err
  357. }
  358. err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second)
  359. if err != nil {
  360. return "", err
  361. }
  362. if len(mountpoint) > 0 {
  363. err := self.Attach(mountpoint)
  364. if err != nil {
  365. return "", err
  366. }
  367. }
  368. return diskId, nil
  369. }
  370. // 华为云不支持重置
  371. func (self *SDisk) Rebuild(ctx context.Context) error {
  372. return cloudprovider.ErrNotSupported
  373. }
  374. func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
  375. if len(diskId) == 0 {
  376. return nil, cloudprovider.ErrNotFound
  377. }
  378. var disk SDisk
  379. err := DoGet(self.ecsClient.Disks.Get, diskId, nil, &disk)
  380. return &disk, err
  381. }
  382. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html
  383. func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) {
  384. queries := map[string]string{}
  385. if len(zoneId) > 0 {
  386. queries["availability_zone"] = zoneId
  387. }
  388. disks := make([]SDisk, 0)
  389. err := doListAllWithOffset(self.ecsClient.Disks.List, queries, &disks)
  390. return disks, err
  391. }
  392. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html
  393. func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (string, error) {
  394. params := jsonutils.NewDict()
  395. volumeObj := jsonutils.NewDict()
  396. volumeObj.Add(jsonutils.NewString(name), "name")
  397. volumeObj.Add(jsonutils.NewString(zoneId), "availability_zone")
  398. volumeObj.Add(jsonutils.NewString(desc), "description")
  399. volumeObj.Add(jsonutils.NewString(category), "volume_type")
  400. volumeObj.Add(jsonutils.NewInt(int64(sizeGb)), "size")
  401. if len(snapshotId) > 0 {
  402. volumeObj.Add(jsonutils.NewString(snapshotId), "snapshot_id")
  403. }
  404. if len(projectId) > 0 {
  405. volumeObj.Add(jsonutils.NewString(projectId), "enterprise_project_id")
  406. }
  407. params.Add(volumeObj, "volume")
  408. // 目前只支持创建按需资源,返回job id。 如果创建包年包月资源则返回order id
  409. _id, err := self.ecsClient.Disks.AsyncCreate(params)
  410. if err != nil {
  411. log.Debugf("AsyncCreate with params: %s", params)
  412. return "", errors.Wrap(err, "AsyncCreate")
  413. }
  414. // 按需计费
  415. volumeId, err := self.GetTaskEntityID(self.ecsClient.Disks.ServiceType(), _id, "volume_id")
  416. if err != nil {
  417. return "", errors.Wrap(err, "GetAllSubTaskEntityIDs")
  418. }
  419. if len(volumeId) == 0 {
  420. return "", errors.Errorf("CreateInstance job %s result is emtpy", _id)
  421. } else {
  422. return volumeId, nil
  423. }
  424. }
  425. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762428.html
  426. // 默认删除云硬盘关联的所有快照
  427. func (self *SRegion) DeleteDisk(diskId string) error {
  428. return DoDeleteWithSpec(self.ecsClient.Disks.DeleteInContextWithSpec, nil, diskId, "", nil, nil)
  429. }
  430. /*
  431. 扩容状态为available的云硬盘时,没有约束限制。
  432. 扩容状态为in-use的云硬盘时,有以下约束:
  433. 不支持共享云硬盘,即multiattach参数值必须为false。
  434. 云硬盘所挂载的云服务器状态必须为ACTIVE、PAUSED、SUSPENDED、SHUTOFF才支持扩容
  435. */
  436. func (self *SRegion) resizeDisk(diskId string, sizeGB int64) error {
  437. params := jsonutils.NewDict()
  438. osExtendObj := jsonutils.NewDict()
  439. osExtendObj.Add(jsonutils.NewInt(sizeGB), "new_size") // GB
  440. params.Add(osExtendObj, "os-extend")
  441. _, err := self.ecsClient.Disks.PerformAction2("action", diskId, params, "")
  442. return err
  443. }
  444. /*
  445. https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html
  446. 只支持快照回滚到源云硬盘,不支持快照回滚到其它指定云硬盘。
  447. 只有云硬盘状态处于“available”或“error_rollbacking”状态才允许快照回滚到源云硬盘。
  448. */
  449. func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) {
  450. params := jsonutils.NewDict()
  451. rollbackObj := jsonutils.NewDict()
  452. rollbackObj.Add(jsonutils.NewString(diskId), "volume_id")
  453. params.Add(rollbackObj, "rollback")
  454. _, err := self.ecsClient.OsSnapshots.PerformAction2("rollback", snapshotId, params, "")
  455. return diskId, err
  456. }
  457. func (self *SDisk) GetProjectId() string {
  458. return self.EnterpriseProjectId
  459. }