disk.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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 qcloud
  15. import (
  16. "context"
  17. "fmt"
  18. "sort"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/utils"
  25. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  26. api "yunion.io/x/cloudmux/pkg/apis/compute"
  27. "yunion.io/x/cloudmux/pkg/cloudprovider"
  28. "yunion.io/x/cloudmux/pkg/multicloud"
  29. )
  30. type Placement struct {
  31. ProjectId int
  32. Zone string
  33. }
  34. type SDisk struct {
  35. storage *SStorage
  36. multicloud.SDisk
  37. QcloudTags
  38. Attached bool
  39. AutoRenewFlagError bool
  40. CreateTime time.Time
  41. DeadlineError bool
  42. DeadlineTime time.Time
  43. DifferDaysOfDeadline int
  44. DiskChargeType string
  45. DiskId string
  46. DiskName string
  47. DiskSize int
  48. DiskState string
  49. DiskType string
  50. DiskUsage string
  51. Encrypt bool
  52. InstanceId string
  53. IsReturnable bool
  54. Placement Placement
  55. Portable bool
  56. RenewFlag string
  57. ReturnFailCode int
  58. RollbackPercent int
  59. Rollbacking bool
  60. SnapshotAbility bool
  61. DeleteWithInstance bool
  62. }
  63. type SDiskSet []SDisk
  64. func (v SDiskSet) Len() int {
  65. return len(v)
  66. }
  67. func (v SDiskSet) Swap(i, j int) {
  68. v[i], v[j] = v[j], v[i]
  69. }
  70. func (v SDiskSet) Less(i, j int) bool {
  71. if v[i].DiskUsage == "SYSTEM_DISK" || v[j].DiskUsage == "DATA_DISK" {
  72. return true
  73. }
  74. return false
  75. }
  76. func (self *SRegion) GetDisks(instanceId string, zoneId string, category string, diskIds []string, offset int, limit int) ([]SDisk, int, error) {
  77. if limit > 50 || limit <= 0 {
  78. limit = 50
  79. }
  80. params := make(map[string]string)
  81. params["Limit"] = fmt.Sprintf("%d", limit)
  82. params["Offset"] = fmt.Sprintf("%d", offset)
  83. filter := 0
  84. if len(zoneId) > 0 {
  85. params[fmt.Sprintf("Filters.%d.Name", filter)] = "zone"
  86. params[fmt.Sprintf("Filters.%d.Values.0", filter)] = zoneId
  87. filter++
  88. }
  89. if len(instanceId) > 0 {
  90. params[fmt.Sprintf("Filters.%d.Name", filter)] = "instance-id"
  91. params[fmt.Sprintf("Filters.%d.Values.0", filter)] = instanceId
  92. filter++
  93. }
  94. if len(category) > 0 {
  95. params[fmt.Sprintf("Filters.%d.Name", filter)] = "disk-type"
  96. params[fmt.Sprintf("Filters.%d.Values.0", filter)] = category
  97. filter++
  98. }
  99. if diskIds != nil && len(diskIds) > 0 {
  100. for index, diskId := range diskIds {
  101. params[fmt.Sprintf("DiskIds.%d", index)] = diskId
  102. }
  103. }
  104. body, err := self.cbsRequest("DescribeDisks", params)
  105. if err != nil {
  106. log.Errorf("GetDisks fail %s", err)
  107. return nil, 0, err
  108. }
  109. disks := make([]SDisk, 0)
  110. err = body.Unmarshal(&disks, "DiskSet")
  111. if err != nil {
  112. log.Errorf("Unmarshal disk details fail %s", err)
  113. return nil, 0, err
  114. }
  115. total, _ := body.Float("TotalCount")
  116. sort.Sort(SDiskSet(disks))
  117. return disks, int(total), nil
  118. }
  119. func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
  120. disks, total, err := self.GetDisks("", "", "", []string{diskId}, 0, 1)
  121. if err != nil {
  122. return nil, err
  123. }
  124. if total != 1 {
  125. return nil, cloudprovider.ErrNotFound
  126. }
  127. return &disks[0], nil
  128. }
  129. func (self *SDisk) GetId() string {
  130. return self.DiskId
  131. }
  132. func (self *SRegion) DeleteDisk(diskIds []string) error {
  133. params := make(map[string]string)
  134. params["Region"] = self.Region
  135. for idx, id := range diskIds {
  136. params[fmt.Sprintf("DiskIds.%d", idx)] = id
  137. }
  138. _, err := self.cbsRequest("TerminateDisks", params)
  139. return err
  140. }
  141. func (self *SDisk) Delete(ctx context.Context) error {
  142. return self.storage.zone.region.DeleteDisk([]string{self.DiskId})
  143. }
  144. func (self *SRegion) ResizeDisk(ctx context.Context, diskId string, sizeGb int64) error {
  145. params := make(map[string]string)
  146. params["DiskId"] = diskId
  147. params["DiskSize"] = fmt.Sprintf("%d", sizeGb)
  148. startTime := time.Now()
  149. for {
  150. _, err := self.cbsRequest("ResizeDisk", params)
  151. if err != nil {
  152. if strings.Index(err.Error(), "Code=InvalidDisk.Busy") > 0 {
  153. log.Infof("The disk is busy, try later ...")
  154. time.Sleep(10 * time.Second)
  155. if time.Now().Sub(startTime) > time.Minute*20 {
  156. return cloudprovider.ErrTimeout
  157. }
  158. continue
  159. }
  160. }
  161. return err
  162. }
  163. }
  164. func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
  165. return self.storage.zone.region.ResizeDisk(ctx, self.DiskId, sizeMb/1024)
  166. }
  167. func (self *SDisk) GetName() string {
  168. if len(self.DiskName) > 0 && self.DiskName != "未命名" {
  169. return self.DiskName
  170. }
  171. return self.DiskId
  172. }
  173. func (self *SDisk) GetGlobalId() string {
  174. return self.DiskId
  175. }
  176. func (self *SDisk) IsEmulated() bool {
  177. return false
  178. }
  179. func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
  180. return self.storage, nil
  181. }
  182. func (self *SDisk) GetStatus() string {
  183. switch self.DiskState {
  184. case "ATTACHING", "DETACHING", "EXPANDING", "ROLLBACKING":
  185. return api.DISK_ALLOCATING
  186. default:
  187. return api.DISK_READY
  188. }
  189. }
  190. func (self *SDisk) Refresh() error {
  191. new, err := self.storage.zone.region.GetDisk(self.DiskId)
  192. if err != nil {
  193. return err
  194. }
  195. return jsonutils.Update(self, new)
  196. }
  197. func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
  198. snapshotId, err := self.storage.zone.region.CreateSnapshot(self.DiskId, name, desc)
  199. if err != nil {
  200. log.Errorf("createSnapshot fail %s", err)
  201. return nil, err
  202. }
  203. snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1)
  204. if err != nil {
  205. return nil, err
  206. }
  207. if total == 1 {
  208. snapshot := &snapshots[0]
  209. err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second)
  210. if err != nil {
  211. return nil, err
  212. }
  213. return snapshot, nil
  214. }
  215. return nil, nil
  216. }
  217. func (self *SDisk) GetDiskType() string {
  218. switch self.DiskUsage {
  219. case "SYSTEM_DISK":
  220. return api.DISK_TYPE_SYS
  221. case "DATA_DISK":
  222. return api.DISK_TYPE_DATA
  223. default:
  224. return api.DISK_TYPE_DATA
  225. }
  226. }
  227. func (self *SDisk) GetFsFormat() string {
  228. return ""
  229. }
  230. func (self *SDisk) GetIsNonPersistent() bool {
  231. return false
  232. }
  233. func (self *SDisk) GetDriver() string {
  234. return "scsi"
  235. }
  236. func (self *SDisk) GetCacheMode() string {
  237. return "none"
  238. }
  239. func (self *SDisk) GetMountpoint() string {
  240. return ""
  241. }
  242. func (self *SDisk) GetBillingType() string {
  243. switch self.DiskChargeType {
  244. case "PREPAID":
  245. return billing_api.BILLING_TYPE_PREPAID
  246. case "POSTPAID_BY_HOUR":
  247. return billing_api.BILLING_TYPE_POSTPAID
  248. default:
  249. return billing_api.BILLING_TYPE_PREPAID
  250. }
  251. }
  252. func (self *SDisk) GetDiskFormat() string {
  253. return "vhd"
  254. }
  255. func (self *SDisk) GetDiskSizeMB() int {
  256. return self.DiskSize * 1024
  257. }
  258. func (self *SDisk) GetIsAutoDelete() bool {
  259. return self.DeleteWithInstance
  260. }
  261. func (self *SDisk) GetCreatedAt() time.Time {
  262. // 2019-12-25 09:00:43 #非UTC时间
  263. return self.CreateTime.Add(time.Hour * -8)
  264. }
  265. func (self *SDisk) GetExpiredAt() time.Time {
  266. if self.DeadlineTime.IsZero() {
  267. return time.Time{}
  268. }
  269. return self.DeadlineTime.Add(time.Hour * -8)
  270. }
  271. func (self *SDisk) SetTags(tags map[string]string, replace bool) error {
  272. return self.storage.zone.region.SetResourceTags("cvm", "volume", []string{self.DiskId}, tags, replace)
  273. }
  274. func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
  275. snapshots, total, err := self.storage.zone.region.GetSnapshots("", "", "", []string{snapshotId}, 0, 1)
  276. if err != nil {
  277. return nil, err
  278. }
  279. if total == 1 {
  280. return &snapshots[0], nil
  281. }
  282. return nil, cloudprovider.ErrNotFound
  283. }
  284. func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
  285. snapshots := make([]SSnapshot, 0)
  286. for {
  287. parts, total, err := self.storage.zone.region.GetSnapshots("", self.DiskId, "", []string{}, 0, 20)
  288. if err != nil {
  289. log.Errorf("GetDisks fail %s", err)
  290. return nil, err
  291. }
  292. snapshots = append(snapshots, parts...)
  293. if len(snapshots) >= total {
  294. break
  295. }
  296. }
  297. isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots))
  298. for i := 0; i < len(snapshots); i++ {
  299. snapshots[i].region = self.storage.zone.region
  300. isnapshots[i] = &snapshots[i]
  301. }
  302. return isnapshots, nil
  303. }
  304. func (self *SDisk) GetTemplateId() string {
  305. //return self.ImageId
  306. return ""
  307. }
  308. func (self *SRegion) ResetDisk(diskId, snapshotId string) error {
  309. params := make(map[string]string)
  310. params["Region"] = self.Region
  311. params["DiskId"] = diskId
  312. params["SnapshotId"] = snapshotId
  313. _, err := self.cbsRequest("ApplySnapshot", params)
  314. if err != nil {
  315. log.Errorf("ResetDisk %s to snapshot %s fail %s", diskId, snapshotId, err)
  316. return err
  317. }
  318. return nil
  319. }
  320. func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
  321. return "", self.storage.zone.region.ResetDisk(self.DiskId, snapshotId)
  322. }
  323. func (self *SRegion) CreateDisk(zoneId string, category string, opts *cloudprovider.DiskCreateConfig) (string, error) {
  324. params := make(map[string]string)
  325. params["Region"] = self.Region
  326. params["DiskType"] = category
  327. params["DiskChargeType"] = "POSTPAID_BY_HOUR"
  328. // [TencentCloudSDKError] Code=InvalidParameter, Message=DiskName: vdisk_stress-testvm-qcloud-1_1560117118026502729, length is 48, out of range [0,20] (e11d6c4007e4), RequestId=a8409994-0357-42e9-b028-e11d6c4007e4
  329. if len(opts.Name) > 20 {
  330. opts.Name = opts.Name[:20]
  331. }
  332. params["DiskName"] = opts.Name
  333. params["Placement.Zone"] = zoneId
  334. if len(opts.ProjectId) > 0 {
  335. params["Placement.ProjectId"] = opts.ProjectId
  336. }
  337. //params["Encrypted"] = "false"
  338. params["DiskSize"] = fmt.Sprintf("%d", opts.SizeGb)
  339. params["ClientToken"] = utils.GenRequestId(20)
  340. tagIdx := 0
  341. for k, v := range opts.Tags {
  342. params[fmt.Sprintf("Tags.%d.Key", tagIdx)] = k
  343. params[fmt.Sprintf("Tags.%d.Value", tagIdx)] = v
  344. tagIdx += 1
  345. }
  346. body, err := self.cbsRequest("CreateDisks", params)
  347. if err != nil {
  348. return "", err
  349. }
  350. diskIDSet := []string{}
  351. err = body.Unmarshal(&diskIDSet, "DiskIdSet")
  352. if err != nil {
  353. return "", err
  354. }
  355. if len(diskIDSet) < 1 {
  356. return "", fmt.Errorf("Create Disk error")
  357. }
  358. return diskIDSet[0], nil
  359. }
  360. func (disk *SDisk) GetAccessPath() string {
  361. return ""
  362. }
  363. func (self *SDisk) Rebuild(ctx context.Context) error {
  364. // TODO
  365. return cloudprovider.ErrNotSupported
  366. }
  367. func (self *SDisk) GetProjectId() string {
  368. return strconv.Itoa(self.Placement.ProjectId)
  369. }