disk.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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 huawei
  15. import (
  16. "context"
  17. "fmt"
  18. "net/url"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  25. api "yunion.io/x/cloudmux/pkg/apis/compute"
  26. "yunion.io/x/cloudmux/pkg/cloudprovider"
  27. "yunion.io/x/cloudmux/pkg/multicloud"
  28. )
  29. /*
  30. 华为云云硬盘
  31. ======创建==========
  32. 1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区
  33. 2.计费模式 包年包月/按需计费
  34. 3.*支持自动备份
  35. 共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html
  36. 根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。
  37. 一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。
  38. 单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。
  39. */
  40. type Attachment struct {
  41. ServerID string `json:"server_id"`
  42. AttachmentID string `json:"attachment_id"`
  43. AttachedAt string `json:"attached_at"`
  44. HostName string `json:"host_name"`
  45. VolumeID string `json:"volume_id"`
  46. Device string `json:"device"`
  47. ID string `json:"id"`
  48. }
  49. type DiskMeta struct {
  50. ResourceSpecCode string `json:"resourceSpecCode"`
  51. Billing string `json:"billing"`
  52. ResourceType string `json:"resourceType"`
  53. AttachedMode string `json:"attached_mode"`
  54. Readonly string `json:"readonly"`
  55. OrderId string `json:"orderID"`
  56. }
  57. type VolumeImageMetadata struct {
  58. QuickStart string `json:"__quick_start"`
  59. ContainerFormat string `json:"container_format"`
  60. MinRAM string `json:"min_ram"`
  61. ImageName string `json:"image_name"`
  62. ImageID string `json:"image_id"`
  63. OSType string `json:"__os_type"`
  64. OSFeatureList string `json:"__os_feature_list"`
  65. MinDisk string `json:"min_disk"`
  66. SupportKVM string `json:"__support_kvm"`
  67. VirtualEnvType string `json:"virtual_env_type"`
  68. SizeGB string `json:"size"`
  69. OSVersion string `json:"__os_version"`
  70. OSBit string `json:"__os_bit"`
  71. SupportKVMHi1822Hiovs string `json:"__support_kvm_hi1822_hiovs"`
  72. SupportXen string `json:"__support_xen"`
  73. Description string `json:"__description"`
  74. Imagetype string `json:"__imagetype"`
  75. DiskFormat string `json:"disk_format"`
  76. ImageSourceType string `json:"__image_source_type"`
  77. Checksum string `json:"checksum"`
  78. Isregistered string `json:"__isregistered"`
  79. HwVifMultiqueueEnabled string `json:"hw_vif_multiqueue_enabled"`
  80. Platform string `json:"__platform"`
  81. }
  82. // https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html
  83. type SDisk struct {
  84. storage *SStorage
  85. multicloud.SDisk
  86. HuaweiDiskTags
  87. ID string `json:"id"`
  88. Name string `json:"name"`
  89. Status string `json:"status"`
  90. Attachments []Attachment `json:"attachments"`
  91. Description string `json:"description"`
  92. SizeGB int `json:"size"`
  93. Metadata DiskMeta `json:"metadata"`
  94. Encrypted bool `json:"encrypted"`
  95. Bootable string `json:"bootable"`
  96. Multiattach bool `json:"multiattach"`
  97. AvailabilityZone string `json:"availability_zone"`
  98. SourceVolid string `json:"source_volid"`
  99. SnapshotID string `json:"snapshot_id"`
  100. CreatedAt time.Time `json:"created_at"`
  101. VolumeType string `json:"volume_type"`
  102. VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"`
  103. ReplicationStatus string `json:"replication_status"`
  104. UserID string `json:"user_id"`
  105. ConsistencygroupID string `json:"consistencygroup_id"`
  106. UpdatedAt string `json:"updated_at"`
  107. EnterpriseProjectId string
  108. ExpiredTime time.Time
  109. }
  110. func (self *SDisk) GetId() string {
  111. return self.ID
  112. }
  113. func (self *SDisk) GetName() string {
  114. if len(self.Name) == 0 {
  115. return self.ID
  116. }
  117. return self.Name
  118. }
  119. func (self *SDisk) GetGlobalId() string {
  120. return self.ID
  121. }
  122. func (self *SDisk) GetStatus() string {
  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. disk, err := self.storage.zone.region.GetDisk(self.GetId())
  160. if err != nil {
  161. return err
  162. }
  163. return jsonutils.Update(self, disk)
  164. }
  165. func (self *SDisk) GetBillingType() string {
  166. if len(self.Metadata.OrderId) > 0 {
  167. return billing_api.BILLING_TYPE_PREPAID
  168. }
  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. orders, err := self.storage.zone.region.client.GetOrderResources()
  176. if err != nil {
  177. return time.Time{}
  178. }
  179. order, ok := orders[self.ID]
  180. if ok {
  181. return order.ExpireTime
  182. }
  183. return time.Time{}
  184. }
  185. func (self *SDisk) GetDeviceName() string {
  186. return self.GetMountpoint()
  187. }
  188. func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
  189. return self.storage, nil
  190. }
  191. func (self *SDisk) GetDiskFormat() string {
  192. return "vhd"
  193. }
  194. func (self *SDisk) GetDiskSizeMB() int {
  195. return int(self.SizeGB * 1024)
  196. }
  197. func (self *SDisk) checkAutoDelete(attachments []Attachment) bool {
  198. autodelete := false
  199. for _, attach := range attachments {
  200. if len(attach.ServerID) > 0 {
  201. // todo : 忽略错误??
  202. vm, err := self.storage.zone.region.GetInstance(attach.ServerID)
  203. if err != nil {
  204. return false
  205. }
  206. volumes := vm.OSExtendedVolumesVolumesAttached
  207. for _, vol := range volumes {
  208. if vol.ID == self.ID && strings.ToLower(vol.DeleteOnTermination) == "true" {
  209. autodelete = true
  210. }
  211. }
  212. break
  213. }
  214. }
  215. return autodelete
  216. }
  217. func (self *SDisk) GetIsAutoDelete() bool {
  218. if len(self.Attachments) > 0 {
  219. return self.checkAutoDelete(self.Attachments)
  220. }
  221. return false
  222. }
  223. func (self *SDisk) GetTemplateId() string {
  224. return self.VolumeImageMetadata.ImageID
  225. }
  226. func (self *SDisk) GetDiskType() string {
  227. if self.Bootable == "true" {
  228. return api.DISK_TYPE_SYS
  229. }
  230. return api.DISK_TYPE_DATA
  231. }
  232. func (self *SDisk) GetFsFormat() string {
  233. return ""
  234. }
  235. func (self *SDisk) GetIsNonPersistent() bool {
  236. return false
  237. }
  238. func (self *SDisk) GetDriver() string {
  239. return "scsi"
  240. }
  241. func (self *SDisk) GetCacheMode() string {
  242. return "none"
  243. }
  244. func (self *SDisk) GetMountpoint() string {
  245. for _, attachment := range self.Attachments {
  246. if len(attachment.Device) > 0 {
  247. return attachment.Device
  248. }
  249. }
  250. return ""
  251. }
  252. func (self *SDisk) GetMountServerId() string {
  253. if len(self.Attachments) > 0 {
  254. return self.Attachments[0].ServerID
  255. }
  256. return ""
  257. }
  258. func (self *SDisk) GetAccessPath() string {
  259. return ""
  260. }
  261. func (self *SDisk) Delete(ctx context.Context) error {
  262. if self.GetBillingType() == billing_api.BILLING_TYPE_PREPAID {
  263. return self.storage.zone.region.CancelResourcesSubscription([]string{self.ID})
  264. }
  265. disk, err := self.storage.zone.region.GetDisk(self.GetId())
  266. if err != nil {
  267. if errors.Cause(err) == cloudprovider.ErrNotFound {
  268. return nil
  269. }
  270. return err
  271. }
  272. if disk.Status != "deleting" {
  273. // 等待硬盘ready
  274. cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second)
  275. err := self.storage.zone.region.DeleteDisk(self.GetId())
  276. if err != nil {
  277. return err
  278. }
  279. }
  280. return cloudprovider.WaitDeleted(self, 10*time.Second, 120*time.Second)
  281. }
  282. func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) {
  283. snapshot, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc)
  284. if err != nil {
  285. return nil, errors.Wrapf(err, "CreateSnapshot")
  286. }
  287. return snapshot, nil
  288. }
  289. func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
  290. snapshot, err := self.storage.zone.region.GetSnapshot(snapshotId)
  291. if err != nil {
  292. return nil, err
  293. }
  294. return snapshot, nil
  295. }
  296. func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
  297. snapshots, err := self.storage.zone.region.GetSnapshots(self.ID, "")
  298. if err != nil {
  299. return nil, err
  300. }
  301. isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots))
  302. for i := 0; i < len(snapshots); i++ {
  303. isnapshots[i] = &snapshots[i]
  304. }
  305. return isnapshots, nil
  306. }
  307. func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
  308. sizeGb := newSizeMB / 1024
  309. err := self.storage.zone.region.ResizeDisk(self.GetId(), sizeGb)
  310. if err != nil {
  311. return err
  312. }
  313. return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 15*time.Second, 5*time.Second, 60*time.Second)
  314. }
  315. func (self *SDisk) Detach() error {
  316. err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId())
  317. if err != nil {
  318. log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
  319. return err
  320. }
  321. return cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool {
  322. err := self.Refresh()
  323. if err != nil {
  324. log.Debugln(err)
  325. return false
  326. }
  327. if self.Status == "available" {
  328. return true
  329. }
  330. return false
  331. })
  332. }
  333. func (self *SDisk) Attach(device string) error {
  334. err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device)
  335. if err != nil {
  336. return errors.Wrapf(err, "AttachDisk")
  337. }
  338. return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 10*time.Second, 5*time.Second, 60*time.Second)
  339. }
  340. func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
  341. return "", cloudprovider.ErrNotSupported
  342. }
  343. func (self *SDisk) Rebuild(ctx context.Context) error {
  344. return cloudprovider.ErrNotSupported
  345. }
  346. // https://console.huaweicloud.com/apiexplorer/#/openapi/EVS/doc?api=ShowVolume
  347. func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
  348. resp, err := self.list(SERVICE_EVS, "cloudvolumes/"+diskId, nil)
  349. if err != nil {
  350. return nil, errors.Wrapf(err, "show volume")
  351. }
  352. ret := &SDisk{}
  353. err = resp.Unmarshal(ret, "volume")
  354. if err != nil {
  355. return nil, err
  356. }
  357. return ret, nil
  358. }
  359. // https://console.huaweicloud.com/apiexplorer/#/openapi/EVS/doc?api=ListVolumes
  360. func (self *SRegion) GetDisks(zoneId, storageTypeId string) ([]SDisk, error) {
  361. ret := []SDisk{}
  362. query := url.Values{}
  363. if len(zoneId) > 0 {
  364. query.Set("availability_zone", zoneId)
  365. }
  366. if len(storageTypeId) > 0 {
  367. query.Set("volume_type_id", storageTypeId)
  368. }
  369. for {
  370. resp, err := self.list(SERVICE_EVS, "cloudvolumes/detail", query)
  371. if err != nil {
  372. return nil, errors.Wrapf(err, "list volumes")
  373. }
  374. part := struct {
  375. Volumes []SDisk
  376. Count int
  377. }{}
  378. err = resp.Unmarshal(&part)
  379. if err != nil {
  380. return nil, errors.Wrapf(err, "Unmarshal")
  381. }
  382. ret = append(ret, part.Volumes...)
  383. if len(ret) >= part.Count || len(part.Volumes) == 0 {
  384. break
  385. }
  386. query.Set("offset", fmt.Sprintf("%d", len(ret)))
  387. }
  388. return ret, nil
  389. }
  390. // https://console.huaweicloud.com/apiexplorer/#/openapi/EVS/doc?api=CreateVolume
  391. func (self *SRegion) CreateDisk(zoneId string, category string, opts *cloudprovider.DiskCreateConfig) (string, error) {
  392. params := map[string]interface{}{
  393. "name": opts.Name,
  394. "availability_zone": zoneId,
  395. "description": opts.Desc,
  396. "volume_type": category,
  397. "size": opts.SizeGb,
  398. }
  399. if len(opts.SnapshotId) > 0 {
  400. params["snapshot_id"] = opts.SnapshotId
  401. }
  402. if len(opts.ProjectId) > 0 {
  403. params["enterprise_project_id"] = opts.ProjectId
  404. }
  405. resp, err := self.post(SERVICE_EVS_V2_1, "cloudvolumes", map[string]interface{}{"volume": params})
  406. if err != nil {
  407. return "", errors.Wrapf(err, "create volume")
  408. }
  409. ret := struct {
  410. JobId string
  411. OrderId string
  412. }{}
  413. err = resp.Unmarshal(&ret)
  414. if err != nil {
  415. return "", errors.Wrapf(err, "Unmarshal")
  416. }
  417. id := ret.JobId + ret.OrderId
  418. // 按需计费
  419. volumeId, err := self.GetTaskEntityID(SERVICE_EVS_V1, id, "volume_id")
  420. if err != nil {
  421. return "", errors.Wrap(err, "GetAllSubTaskEntityIDs")
  422. }
  423. if len(volumeId) == 0 {
  424. return "", errors.Errorf("CreateInstance job %s result is emtpy", id)
  425. }
  426. return volumeId, nil
  427. }
  428. // https://console.huaweicloud.com/apiexplorer/#/openapi/EVS/doc?api=DeleteVolume
  429. func (self *SRegion) DeleteDisk(diskId string) error {
  430. res := fmt.Sprintf("cloudvolumes/%s", diskId)
  431. _, err := self.delete(SERVICE_EVS, res)
  432. return err
  433. }
  434. // https://console.huaweicloud.com/apiexplorer/#/openapi/EVS/doc?api=ResizeVolume
  435. func (self *SRegion) ResizeDisk(diskId string, sizeGB int64) error {
  436. params := map[string]interface{}{
  437. "os-extend": map[string]interface{}{
  438. "new_size": sizeGB,
  439. },
  440. }
  441. _, err := self.post(SERVICE_EVS_V2_1, fmt.Sprintf("cloudvolumes/%s/action", diskId), params)
  442. return err
  443. }
  444. func (self *SDisk) GetProjectId() string {
  445. return self.EnterpriseProjectId
  446. }