disk.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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 azure
  15. import (
  16. "context"
  17. "fmt"
  18. "net/url"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  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 DiskSku struct {
  31. Name string `json:"name,omitempty"`
  32. Tier string `json:"tier,omitempty"`
  33. }
  34. type ImageDiskReference struct {
  35. ID string
  36. Lun int32 `json:"lun,omitempty"`
  37. }
  38. type CreationData struct {
  39. CreateOption string `json:"createOption,omitempty"`
  40. StorageAccountID string
  41. // ImageReference *ImageDiskReference `json:"imageReference,omitempty"`
  42. ImageReference *ImageReference `json:"imageReference,omitempty"`
  43. SourceURI string `json:"sourceUri,omitempty"`
  44. SourceResourceID string `json:"sourceResourceId,omitempty"`
  45. }
  46. type TAzureInt32 string
  47. func (ai TAzureInt32) Int32() int32 {
  48. num, _ := strconv.Atoi(strings.Trim(string(ai), "\t"))
  49. return int32(num)
  50. }
  51. type DiskProperties struct {
  52. TimeCreated time.Time `json:"timeCreated,omitempty"`
  53. OsType string `json:"osType,omitempty"`
  54. CreationData CreationData `json:"creationData,omitempty"`
  55. DiskSizeGB TAzureInt32 `json:"diskSizeGB,omitempty"`
  56. ProvisioningState string `json:"provisioningState,omitempty"`
  57. DiskState string `json:"diskState,omitempty"`
  58. }
  59. type SDisk struct {
  60. storage *SStorage
  61. multicloud.SDisk
  62. AzureTags
  63. ManagedBy string `json:"managedBy,omitempty"`
  64. Sku DiskSku `json:"sku,omitempty"`
  65. Zones []string `json:"zones,omitempty"`
  66. ID string `json:"id,omitempty"`
  67. Name string `json:"name,omitempty"`
  68. Type string `json:"type,omitempty"`
  69. Location string `json:"location,omitempty"`
  70. Properties DiskProperties `json:"properties,omitempty"`
  71. }
  72. func (self *SRegion) CreateDisk(storageType string, opts *cloudprovider.DiskCreateConfig) (*SDisk, error) {
  73. params := jsonutils.Marshal(map[string]interface{}{
  74. "Name": opts.Name,
  75. "Location": self.Name,
  76. "Sku": map[string]string{
  77. "Name": storageType,
  78. },
  79. "Type": "Microsoft.Compute/disks",
  80. }).(*jsonutils.JSONDict)
  81. properties := map[string]interface{}{
  82. "CreationData": map[string]string{
  83. "CreateOption": "Empty",
  84. },
  85. "DiskSizeGB": opts.SizeGb,
  86. }
  87. if len(opts.ImageId) > 0 {
  88. image, err := self.GetImageById(opts.ImageId)
  89. if err != nil {
  90. return nil, errors.Wrapf(err, "GetImageById(%s)", opts.ImageId)
  91. }
  92. // 通过镜像创建的磁盘只能传ID参数,不能通过sku,offer等参数创建.
  93. imageId, err := self.getOfferedImageId(&image)
  94. if err != nil {
  95. return nil, errors.Wrapf(err, "getOfferedImageId")
  96. }
  97. properties = map[string]interface{}{
  98. "CreationData": map[string]interface{}{
  99. "CreateOption": "FromImage",
  100. "ImageReference": map[string]string{
  101. "Id": imageId,
  102. },
  103. },
  104. }
  105. } else if len(opts.SnapshotId) > 0 {
  106. properties = map[string]interface{}{
  107. "CreationData": map[string]interface{}{
  108. "CreateOption": "Copy",
  109. "sourceResourceId": opts.SnapshotId,
  110. },
  111. }
  112. }
  113. params.Add(jsonutils.Marshal(properties), "Properties")
  114. disk := &SDisk{}
  115. return disk, self.create(opts.ProjectId, params, disk)
  116. }
  117. func (self *SRegion) DeleteDisk(diskId string) error {
  118. return cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) {
  119. err := self.del(diskId)
  120. if err == nil {
  121. return true, nil
  122. }
  123. // Disk vdisk_stress-testvm-azure-1-1_1555940308395625000 is attached to VM /subscriptions/d4f0ec08-3e28-4ae5-bdf9-3dc7c5b0eeca/resourceGroups/Default/providers/Microsoft.Compute/virtualMachines/stress-testvm-azure-1.
  124. // 更换系统盘后,数据未刷新会出现如上错误,多尝试几次即可
  125. if strings.Contains(err.Error(), "is attached to VM") {
  126. return false, nil
  127. }
  128. return false, err
  129. })
  130. }
  131. func (self *SRegion) ResizeDisk(diskId string, sizeGb int32) error {
  132. disk, err := self.GetDisk(diskId)
  133. if err != nil {
  134. return err
  135. }
  136. disk.Properties.DiskSizeGB = TAzureInt32(fmt.Sprintf("%d", sizeGb))
  137. disk.Properties.ProvisioningState = ""
  138. return self.update(jsonutils.Marshal(disk), nil)
  139. }
  140. func (self *SRegion) GetDisk(diskId string) (*SDisk, error) {
  141. disk := SDisk{}
  142. return &disk, self.get(diskId, url.Values{}, &disk)
  143. }
  144. func (self *SRegion) GetDisks() ([]SDisk, error) {
  145. disks := []SDisk{}
  146. err := self.list("Microsoft.Compute/disks", url.Values{}, &disks)
  147. if err != nil {
  148. return nil, err
  149. }
  150. return disks, nil
  151. }
  152. func (self *SDisk) GetTags() (map[string]string, error) {
  153. return self.Tags, nil
  154. }
  155. func (self *SDisk) GetStatus() string {
  156. // 为了不统计这种磁盘挂载率, 单独设置一个状态
  157. if self.Properties.DiskState == "ActiveSAS" {
  158. return self.Properties.DiskState
  159. }
  160. status := self.Properties.ProvisioningState
  161. switch status {
  162. case "Updating":
  163. return api.DISK_ALLOCATING
  164. case "Succeeded":
  165. return api.DISK_READY
  166. default:
  167. log.Errorf("Unknow azure disk %s status: %s", self.ID, status)
  168. return api.DISK_UNKNOWN
  169. }
  170. }
  171. func (self *SDisk) GetId() string {
  172. return self.ID
  173. }
  174. func (self *SDisk) Refresh() error {
  175. disk, err := self.storage.zone.region.GetDisk(self.ID)
  176. if err != nil {
  177. return errors.Wrapf(err, "GetDisk(%s)", self.ID)
  178. }
  179. return jsonutils.Update(self, disk)
  180. }
  181. func (self *SDisk) Delete(ctx context.Context) error {
  182. return self.storage.zone.region.DeleteDisk(self.ID)
  183. }
  184. func (self *SDisk) Resize(ctx context.Context, sizeMb int64) error {
  185. return self.storage.zone.region.ResizeDisk(self.ID, int32(sizeMb/1024))
  186. }
  187. func (self *SDisk) GetName() string {
  188. if len(self.Name) > 0 {
  189. return self.Name
  190. }
  191. return self.ID
  192. }
  193. func (self *SDisk) GetGlobalId() string {
  194. return strings.ToLower(self.ID)
  195. }
  196. func (self *SDisk) IsEmulated() bool {
  197. return false
  198. }
  199. func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
  200. return self.storage, nil
  201. }
  202. func (self *SDisk) GetFsFormat() string {
  203. return ""
  204. }
  205. func (self *SDisk) GetIsNonPersistent() bool {
  206. return false
  207. }
  208. func (self *SDisk) GetDriver() string {
  209. return "scsi"
  210. }
  211. func (self *SDisk) GetCacheMode() string {
  212. return "none"
  213. }
  214. func (self *SDisk) GetMountpoint() string {
  215. return ""
  216. }
  217. func (self *SDisk) GetDiskFormat() string {
  218. return "vhd"
  219. }
  220. func (self *SDisk) GetDiskSizeMB() int {
  221. return int(self.Properties.DiskSizeGB.Int32()) * 1024
  222. }
  223. func (self *SDisk) GetIsAutoDelete() bool {
  224. return false
  225. }
  226. func (self *SDisk) GetTemplateId() string {
  227. if self.Properties.CreationData.ImageReference != nil {
  228. return self.Properties.CreationData.ImageReference.ID
  229. }
  230. return ""
  231. }
  232. func (self *SDisk) GetDiskType() string {
  233. if len(self.Properties.OsType) > 0 {
  234. return api.DISK_TYPE_SYS
  235. }
  236. return api.DISK_TYPE_DATA
  237. }
  238. func (self *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
  239. snapshot, err := self.storage.zone.region.CreateSnapshot(self.ID, name, desc)
  240. if err != nil {
  241. return nil, errors.Wrapf(err, "CreateSnapshot")
  242. }
  243. return snapshot, nil
  244. }
  245. func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
  246. snapshots, err := self.storage.zone.region.ListSnapshots()
  247. if err != nil {
  248. return nil, errors.Wrapf(err, "ListSnapshots")
  249. }
  250. ret := []cloudprovider.ICloudSnapshot{}
  251. for i := range snapshots {
  252. if strings.ToLower(snapshots[i].Properties.CreationData.SourceResourceID) == strings.ToLower(self.ID) {
  253. snapshots[i].region = self.storage.zone.region
  254. ret = append(ret, &snapshots[i])
  255. }
  256. }
  257. return ret, nil
  258. }
  259. func (self *SDisk) GetBillingType() string {
  260. return billing_api.BILLING_TYPE_POSTPAID
  261. }
  262. func (self *SDisk) GetCreatedAt() time.Time {
  263. return self.Properties.TimeCreated
  264. }
  265. func (self *SDisk) GetExpiredAt() time.Time {
  266. return time.Time{}
  267. }
  268. func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
  269. if self.Properties.DiskState != "Unattached" {
  270. return "", fmt.Errorf("Azure reset disk needs to be done in the Unattached state, current status: %s", self.Properties.DiskState)
  271. }
  272. opts := &cloudprovider.DiskCreateConfig{
  273. Name: self.Name,
  274. SizeGb: 0,
  275. SnapshotId: snapshotId,
  276. ProjectId: self.GetProjectId(),
  277. }
  278. disk, err := self.storage.zone.region.CreateDisk(self.Sku.Name, opts)
  279. if err != nil {
  280. return "", errors.Wrap(err, "CreateDisk")
  281. }
  282. err = self.storage.zone.region.DeleteDisk(self.ID)
  283. if err != nil {
  284. log.Warningf("delete old disk %s error: %v", self.ID, err)
  285. }
  286. return disk.ID, nil
  287. }
  288. func (disk *SDisk) GetAccessPath() string {
  289. return ""
  290. }
  291. func (self *SDisk) Rebuild(ctx context.Context) error {
  292. // TODO
  293. return cloudprovider.ErrNotSupported
  294. }
  295. func (self *SDisk) GetProjectId() string {
  296. return getResourceGroup(self.ID)
  297. }