disk.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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 aliyun
  15. import (
  16. "context"
  17. "fmt"
  18. "time"
  19. "yunion.io/x/jsonutils"
  20. "yunion.io/x/log"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/utils"
  23. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  24. api "yunion.io/x/cloudmux/pkg/apis/compute"
  25. "yunion.io/x/cloudmux/pkg/cloudprovider"
  26. "yunion.io/x/cloudmux/pkg/multicloud"
  27. )
  28. type SMountInstances struct {
  29. MountInstance []string
  30. }
  31. type SDisk struct {
  32. storage *SStorage
  33. multicloud.SDisk
  34. AliyunTags
  35. IOPS int
  36. AttachedTime time.Time
  37. AutoSnapshotPolicyId string
  38. Category string
  39. PerformanceLevel string
  40. CreationTime time.Time
  41. DeleteAutoSnapshot bool
  42. DeleteWithInstance bool
  43. Description string
  44. DetachedTime time.Time
  45. Device string
  46. DiskChargeType TChargeType
  47. DiskId string
  48. DiskName string
  49. EnableAutoSnapshot bool
  50. EnableAutomatedSnapshotPolicy bool
  51. Encrypted bool
  52. ExpiredTime time.Time
  53. ImageId string
  54. InstanceId string
  55. MountInstances SMountInstances
  56. OperationLocks SOperationLocks
  57. Portable bool
  58. ProductCode string
  59. RegionId string
  60. ResourceGroupId string
  61. Size int
  62. SourceSnapshotId string
  63. Status string
  64. Type string
  65. ZoneId string
  66. }
  67. func (self *SDisk) GetIops() int {
  68. return self.IOPS
  69. }
  70. func (self *SRegion) GetDisks(instanceId string, zoneId string, category string, diskIds []string, snapshotpolicyId string) ([]SDisk, error) {
  71. params := make(map[string]string)
  72. params["RegionId"] = self.RegionId
  73. params["MaxResults"] = "500"
  74. if len(instanceId) > 0 {
  75. params["InstanceId"] = instanceId
  76. }
  77. if len(zoneId) > 0 {
  78. params["ZoneId"] = zoneId
  79. }
  80. if len(category) > 0 {
  81. params["Category"] = category
  82. }
  83. if len(diskIds) > 0 {
  84. params["DiskIds"] = jsonutils.Marshal(diskIds).String()
  85. }
  86. if len(snapshotpolicyId) > 0 {
  87. params["AutoSnapshotPolicyId"] = snapshotpolicyId
  88. }
  89. ret := []SDisk{}
  90. for {
  91. resp, err := self.ecsRequest("DescribeDisks", params)
  92. if err != nil {
  93. return nil, errors.Wrapf(err, "DescribeDisks")
  94. }
  95. part := struct {
  96. Disks struct {
  97. Disk []SDisk
  98. }
  99. NextToken string
  100. }{}
  101. err = resp.Unmarshal(&part)
  102. if err != nil {
  103. return nil, errors.Wrapf(err, "Unmarshal")
  104. }
  105. ret = append(ret, part.Disks.Disk...)
  106. if len(part.NextToken) == 0 || len(part.Disks.Disk) == 0 {
  107. break
  108. }
  109. params["NextToken"] = part.NextToken
  110. }
  111. return ret, nil
  112. }
  113. func (self *SDisk) GetId() string {
  114. return self.DiskId
  115. }
  116. func (self *SDisk) Delete(ctx context.Context) error {
  117. _, err := self.storage.zone.region.getDisk(self.DiskId)
  118. if err != nil {
  119. if errors.Cause(err) == cloudprovider.ErrNotFound {
  120. // 未找到disk, 说明disk已经被删除了. 避免回收站中disk-delete循环删除失败
  121. return nil
  122. }
  123. log.Errorf("Failed to find disk %s when delete: %s", self.DiskId, err)
  124. return err
  125. }
  126. for {
  127. err := self.storage.zone.region.DeleteDisk(self.DiskId)
  128. if err != nil {
  129. if isError(err, "IncorrectDiskStatus") {
  130. log.Infof("The disk is initializing, try later ...")
  131. time.Sleep(10 * time.Second)
  132. } else {
  133. log.Errorf("DeleteDisk fail: %s", err)
  134. return err
  135. }
  136. } else {
  137. break
  138. }
  139. }
  140. return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes
  141. }
  142. func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
  143. return self.storage.zone.region.resizeDisk(self.DiskId, sizeMb)
  144. }
  145. func (self *SDisk) ChangeBillingType(billingType string) error {
  146. return self.storage.zone.region.ChangeDiskChargeType(self.InstanceId, self.DiskId, billingType)
  147. }
  148. func (self *SDisk) SetTags(tags map[string]string, replace bool) error {
  149. return self.storage.zone.region.SetResourceTags(ALIYUN_SERVICE_ECS, "disk", self.DiskId, tags, replace)
  150. }
  151. func (self *SDisk) GetDeviceName() string {
  152. return self.Device
  153. }
  154. func (self *SRegion) ChangeDiskChargeType(vmId, diskId string, billingType string) error {
  155. params := make(map[string]string)
  156. params["RegionId"] = self.RegionId
  157. params["DiskIds"] = jsonutils.Marshal([]string{diskId}).String()
  158. switch billingType {
  159. case billing_api.BILLING_TYPE_POSTPAID:
  160. params["DiskChargeType"] = "PostPaid"
  161. case billing_api.BILLING_TYPE_PREPAID:
  162. params["DiskChargeType"] = "PrePaid"
  163. }
  164. params["AutoPay"] = "true"
  165. params["ClientToken"] = utils.GenRequestId(20)
  166. params["InstanceId"] = vmId
  167. _, err := self.ecsRequest("ModifyDiskChargeType", params)
  168. return err
  169. }
  170. func (self *SDisk) GetName() string {
  171. if len(self.DiskName) > 0 {
  172. return self.DiskName
  173. }
  174. return self.DiskId
  175. }
  176. func (self *SDisk) GetGlobalId() string {
  177. return self.DiskId
  178. }
  179. func (self *SDisk) IsEmulated() bool {
  180. return false
  181. }
  182. func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
  183. return self.storage, nil
  184. }
  185. func (self *SDisk) GetStatus() string {
  186. // In_use Available Attaching Detaching Creating ReIniting All
  187. switch self.Status {
  188. case "Creating", "ReIniting":
  189. return api.DISK_ALLOCATING
  190. default:
  191. return api.DISK_READY
  192. }
  193. }
  194. func (self *SDisk) Refresh() error {
  195. new, err := self.storage.zone.region.getDisk(self.DiskId)
  196. if err != nil {
  197. return err
  198. }
  199. return jsonutils.Update(self, new)
  200. }
  201. func (self *SDisk) ResizeDisk(newSize int64) error {
  202. // newSize 单位为 GB. 范围在20 ~2000. 只能往大调。不能调小
  203. // https://help.aliyun.com/document_detail/25522.html?spm=a2c4g.11174283.6.897.aHwqkS
  204. return self.storage.zone.region.resizeDisk(self.DiskId, newSize)
  205. }
  206. func (self *SDisk) GetDiskFormat() string {
  207. return "vhd"
  208. }
  209. func (self *SDisk) GetDiskSizeMB() int {
  210. return self.Size * 1024
  211. }
  212. func (self *SDisk) GetIsAutoDelete() bool {
  213. return self.DeleteWithInstance
  214. }
  215. func (self *SDisk) GetTemplateId() string {
  216. return self.ImageId
  217. }
  218. func (self *SDisk) GetDiskType() string {
  219. switch self.Type {
  220. case "system":
  221. return api.DISK_TYPE_SYS
  222. case "data":
  223. return api.DISK_TYPE_DATA
  224. default:
  225. return api.DISK_TYPE_DATA
  226. }
  227. }
  228. func (self *SDisk) GetFsFormat() string {
  229. return ""
  230. }
  231. func (self *SDisk) GetIsNonPersistent() bool {
  232. return false
  233. }
  234. func (self *SDisk) GetDriver() string {
  235. return "scsi"
  236. }
  237. func (self *SDisk) GetCacheMode() string {
  238. return "none"
  239. }
  240. func (self *SDisk) GetMountpoint() string {
  241. return ""
  242. }
  243. func (self *SRegion) CreateDisk(zoneId string, category string, opts *cloudprovider.DiskCreateConfig) (string, error) {
  244. params := make(map[string]string)
  245. params["ZoneId"] = zoneId
  246. params["DiskName"] = opts.Name
  247. if len(opts.Desc) > 0 {
  248. params["Description"] = opts.Desc
  249. }
  250. params["Encrypted"] = "false"
  251. params["DiskCategory"] = category
  252. if category == api.STORAGE_CLOUD_ESSD_PL0 {
  253. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  254. params["PerformanceLevel"] = "PL0"
  255. }
  256. if category == api.STORAGE_CLOUD_ESSD_PL2 {
  257. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  258. params["PerformanceLevel"] = "PL2"
  259. }
  260. if category == api.STORAGE_CLOUD_ESSD_PL3 {
  261. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  262. params["PerformanceLevel"] = "PL3"
  263. }
  264. if category == api.STORAGE_CLOUD_AUTO {
  265. params["BurstingEnabled"] = "true"
  266. }
  267. if len(opts.ProjectId) > 0 {
  268. params["ResourceGroupId"] = opts.ProjectId
  269. }
  270. tagIdx := 1
  271. for k, v := range opts.Tags {
  272. params[fmt.Sprintf("Tag.%d.Key", tagIdx)] = k
  273. params[fmt.Sprintf("Tag.%d.Value", tagIdx)] = v
  274. tagIdx += 1
  275. }
  276. params["Size"] = fmt.Sprintf("%d", opts.SizeGb)
  277. params["ClientToken"] = utils.GenRequestId(20)
  278. body, err := self.ecsRequest("CreateDisk", params)
  279. if err != nil {
  280. return "", err
  281. }
  282. return body.GetString("DiskId")
  283. }
  284. func (self *SRegion) getDisk(diskId string) (*SDisk, error) {
  285. disks, err := self.GetDisks("", "", "", []string{diskId}, "")
  286. if err != nil {
  287. return nil, err
  288. }
  289. for i := range disks {
  290. if disks[i].DiskId == diskId {
  291. return &disks[i], nil
  292. }
  293. }
  294. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", diskId)
  295. }
  296. func (self *SRegion) DeleteDisk(diskId string) error {
  297. params := make(map[string]string)
  298. params["DiskId"] = diskId
  299. _, err := self.ecsRequest("DeleteDisk", params)
  300. return err
  301. }
  302. func (self *SRegion) resizeDisk(diskId string, sizeMb int64) error {
  303. sizeGb := sizeMb / 1024
  304. params := make(map[string]string)
  305. params["DiskId"] = diskId
  306. params["Type"] = "online"
  307. params["NewSize"] = fmt.Sprintf("%d", sizeGb)
  308. _, err := self.ecsRequest("ResizeDisk", params)
  309. if err != nil {
  310. return errors.Wrapf(err, "ResizeDisk %d GB", sizeGb)
  311. }
  312. return nil
  313. }
  314. func (self *SRegion) resetDisk(diskId, snapshotId string) error {
  315. params := make(map[string]string)
  316. params["DiskId"] = diskId
  317. params["SnapshotId"] = snapshotId
  318. _, err := self.ecsRequest("ResetDisk", params)
  319. if err != nil {
  320. log.Errorf("ResetDisk %s to snapshot %s fail %s", diskId, snapshotId, err)
  321. return err
  322. }
  323. return nil
  324. }
  325. func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
  326. snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc)
  327. if err != nil {
  328. return nil, errors.Wrapf(err, "CreateSnapshot")
  329. }
  330. snapshot, err := self.storage.zone.region.GetISnapshotById(snapshotId)
  331. if err != nil {
  332. return nil, errors.Wrapf(err, "getSnapshot(%s)", snapshotId)
  333. }
  334. err = cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second)
  335. if err != nil {
  336. return nil, errors.Wrapf(err, "cloudprovider.WaitStatus")
  337. }
  338. return snapshot, nil
  339. }
  340. func (self *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) {
  341. params := make(map[string]string)
  342. params["RegionId"] = self.RegionId
  343. params["DiskId"] = diskId
  344. params["SnapshotName"] = name
  345. params["Description"] = desc
  346. if body, err := self.ecsRequest("CreateSnapshot", params); err != nil {
  347. log.Errorf("CreateSnapshot fail %s", err)
  348. return "", err
  349. } else {
  350. return body.GetString("SnapshotId")
  351. }
  352. }
  353. func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
  354. return self.storage.zone.region.GetISnapshotById(snapshotId)
  355. }
  356. func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
  357. snapshots := make([]SSnapshot, 0)
  358. for {
  359. parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, len(snapshots), 20)
  360. if err != nil {
  361. return nil, errors.Wrapf(err, "GetSnapshots(%s)", self.DiskId)
  362. }
  363. snapshots = append(snapshots, parts...)
  364. if len(snapshots) >= total {
  365. break
  366. }
  367. }
  368. isnapshots := []cloudprovider.ICloudSnapshot{}
  369. for i := 0; i < len(snapshots); i++ {
  370. snapshots[i].region = self.storage.zone.region
  371. isnapshots = append(isnapshots, &snapshots[i])
  372. }
  373. return isnapshots, nil
  374. }
  375. func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
  376. return "", self.storage.zone.region.resetDisk(self.DiskId, snapshotId)
  377. }
  378. func (self *SDisk) GetBillingType() string {
  379. return convertChargeType(self.DiskChargeType)
  380. }
  381. func (self *SDisk) GetCreatedAt() time.Time {
  382. return self.CreationTime
  383. }
  384. func (self *SDisk) GetExpiredAt() time.Time {
  385. return convertExpiredAt(self.ExpiredTime)
  386. }
  387. func (self *SDisk) GetAccessPath() string {
  388. return ""
  389. }
  390. func (self *SDisk) Rebuild(ctx context.Context) error {
  391. err := self.storage.zone.region.rebuildDisk(self.DiskId)
  392. if err != nil {
  393. if isError(err, "IncorrectInstanceStatus") {
  394. return nil
  395. }
  396. log.Errorf("rebuild disk fail %s", err)
  397. return err
  398. }
  399. return nil
  400. }
  401. func (self *SRegion) rebuildDisk(diskId string) error {
  402. params := make(map[string]string)
  403. params["DiskId"] = diskId
  404. _, err := self.ecsRequest("ReInitDisk", params)
  405. if err != nil {
  406. log.Errorf("ReInitDisk %s fail %s", diskId, err)
  407. return err
  408. }
  409. return nil
  410. }
  411. func (self *SDisk) GetProjectId() string {
  412. return self.ResourceGroupId
  413. }
  414. func (region *SRegion) ChagneDiskStorage(ctx context.Context, opts *cloudprovider.ChangeStorageOptions) error {
  415. params := map[string]string{
  416. "DiskId": opts.DiskId,
  417. "DiskCategory": opts.StorageType,
  418. }
  419. switch opts.StorageType {
  420. case api.STORAGE_CLOUD_ESSD_PL0:
  421. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  422. params["PerformanceLevel"] = "PL0"
  423. case api.STORAGE_CLOUD_ESSD_PL2:
  424. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  425. params["PerformanceLevel"] = "PL2"
  426. case api.STORAGE_CLOUD_ESSD_PL3:
  427. params["DiskCategory"] = api.STORAGE_CLOUD_ESSD
  428. params["PerformanceLevel"] = "PL3"
  429. }
  430. _, err := region.ecsRequest("ModifyDiskSpec", params)
  431. return err
  432. }
  433. func (disk *SDisk) ChangeStorage(ctx context.Context, opts *cloudprovider.ChangeStorageOptions) error {
  434. opts.DiskId = disk.DiskId
  435. return disk.storage.zone.region.ChagneDiskStorage(ctx, opts)
  436. }