disks.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 compute
  15. import (
  16. "compress/zlib"
  17. "context"
  18. "fmt"
  19. "io"
  20. "net/http"
  21. "os"
  22. "strconv"
  23. "time"
  24. "github.com/cheggaaa/pb/v3"
  25. "yunion.io/x/jsonutils"
  26. "yunion.io/x/log"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/util/httputils"
  29. "yunion.io/x/onecloud/cmd/climc/shell"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  32. "yunion.io/x/onecloud/pkg/mcclient/options"
  33. compute_options "yunion.io/x/onecloud/pkg/mcclient/options/compute"
  34. "yunion.io/x/onecloud/pkg/util/sparsefile"
  35. )
  36. func init() {
  37. cmd := shell.NewResourceCmd(&modules.Disks)
  38. cmd.List(&compute_options.DiskListOptions{})
  39. cmd.Show(&compute_options.DiskIdOptions{})
  40. cmd.Perform("public", &compute_options.DiskIdOptions{})
  41. cmd.Perform("private", &compute_options.DiskIdOptions{})
  42. cmd.Perform("syncstatus", &compute_options.DiskIdOptions{})
  43. cmd.Perform("change-owner-candidate-domains", &compute_options.DiskIdOptions{})
  44. cmd.Perform("disk-cancel-delete", &compute_options.DiskIdOptions{})
  45. cmd.Perform("set-class-metadata", &options.ResourceMetadataOptions{})
  46. cmd.Perform("rebuild", &compute_options.DiskRebuildOptions{})
  47. cmd.Perform("migrate", &compute_options.DiskMigrateOptions{})
  48. cmd.Perform("reset-template", &compute_options.DiskResetTemplateOptions{})
  49. cmd.Perform("change-billing-type", new(compute_options.DiskChangeBillingTypeOptions))
  50. cmd.Perform("change-storage-type", &compute_options.DiskChangeStorageTypeOptions{})
  51. type DiskDeleteOptions struct {
  52. ID []string `help:"ID of disks to delete" metavar:"DISK"`
  53. OverridePendingDelete bool `help:"Delete disk directly instead of pending delete" short-token:"f"`
  54. DeleteSnapshots bool `help:"Delete disk snapshots before delete disk"`
  55. }
  56. R(&DiskDeleteOptions{}, "disk-delete", "Delete a disk", func(s *mcclient.ClientSession, args *DiskDeleteOptions) error {
  57. params := jsonutils.NewDict()
  58. if args.OverridePendingDelete {
  59. params.Add(jsonutils.JSONTrue, "override_pending_delete")
  60. }
  61. if args.DeleteSnapshots {
  62. params.Add(jsonutils.JSONTrue, "delete_snapshots")
  63. }
  64. ret := modules.Disks.BatchDeleteWithParam(s, args.ID, params, nil)
  65. printBatchResults(ret, modules.Disks.GetColumns(s))
  66. return nil
  67. })
  68. type DiskBatchOpsOptions struct {
  69. ID []string `help:"id list of disks to operate"`
  70. }
  71. R(&DiskBatchOpsOptions{}, "disk-purge", "Delete a disk record in database, not actually do deletion", func(s *mcclient.ClientSession, args *DiskBatchOpsOptions) error {
  72. ret := modules.Disks.BatchPerformAction(s, args.ID, "purge", nil)
  73. printBatchResults(ret, modules.Disks.GetColumns(s))
  74. return nil
  75. })
  76. R(&compute_options.DiskIdOptions{}, "disk-metadata", "Get metadata of a disk", func(s *mcclient.ClientSession, args *compute_options.DiskIdOptions) error {
  77. meta, e := modules.Disks.GetMetadata(s, args.ID, nil)
  78. if e != nil {
  79. return e
  80. }
  81. printObject(meta)
  82. return nil
  83. })
  84. type DiskUpdateOptions struct {
  85. ID string `help:"ID or name of disk"`
  86. Name string `help:"New name of disk"`
  87. Desc string `help:"Description" metavar:"DESCRIPTION"`
  88. AutoDelete string `help:"Set disk auto_delete (true/false or enable/disable)" choices:"true|false|enable|disable"`
  89. AutoSnapshot string `help:"enable/disable auto snapshot of disk" choices:"enable|disable"`
  90. DiskType string `help:"Disk type" choices:"data|volume|sys"`
  91. IsSsd *bool `help:"mark disk as ssd" negative:"no-is-ssd"`
  92. AutoReset *bool `help:"Enable auto reset disk after geust shutdown"`
  93. }
  94. R(&DiskUpdateOptions{}, "disk-update", "Update property of a virtual disk", func(s *mcclient.ClientSession, args *DiskUpdateOptions) error {
  95. params := jsonutils.NewDict()
  96. if len(args.Name) > 0 {
  97. params.Add(jsonutils.NewString(args.Name), "name")
  98. }
  99. if len(args.Desc) > 0 {
  100. params.Add(jsonutils.NewString(args.Desc), "description")
  101. }
  102. if len(args.AutoDelete) > 0 {
  103. if args.AutoDelete == "enable" || args.AutoDelete == "true" {
  104. params.Add(jsonutils.JSONTrue, "auto_delete")
  105. } else {
  106. params.Add(jsonutils.JSONFalse, "auto_delete")
  107. }
  108. }
  109. if len(args.AutoSnapshot) > 0 {
  110. if args.AutoSnapshot == "enable" {
  111. params.Add(jsonutils.JSONTrue, "auto_snapshot")
  112. } else {
  113. params.Add(jsonutils.JSONFalse, "auto_snapshot")
  114. }
  115. }
  116. if len(args.DiskType) > 0 {
  117. params.Add(jsonutils.NewString(args.DiskType), "disk_type")
  118. }
  119. if args.IsSsd != nil {
  120. if *args.IsSsd {
  121. params.Add(jsonutils.JSONTrue, "is_ssd")
  122. } else {
  123. params.Add(jsonutils.JSONFalse, "is_ssd")
  124. }
  125. }
  126. if args.AutoReset != nil {
  127. params.Add(jsonutils.NewBool(*args.AutoReset), "auto_reset")
  128. }
  129. if params.Size() == 0 {
  130. return InvalidUpdateError()
  131. }
  132. disk, e := modules.Disks.Update(s, args.ID, params)
  133. if e != nil {
  134. return e
  135. }
  136. printObject(disk)
  137. return nil
  138. })
  139. R(&compute_options.DiskCreateOptions{}, "disk-create", "Create a virtual disk", func(s *mcclient.ClientSession, args *compute_options.DiskCreateOptions) error {
  140. params, err := args.Params()
  141. if err != nil {
  142. return err
  143. }
  144. if args.TaskNotify {
  145. s.PrepareTask()
  146. }
  147. if args.Count > 1 {
  148. results := modules.Disks.BatchCreate(s, params.JSON(params), args.Count)
  149. printBatchResults(results, modules.Disks.GetColumns(s))
  150. } else {
  151. disk, err := modules.Disks.Create(s, params.JSON(params))
  152. if err != nil {
  153. return err
  154. }
  155. printObject(disk)
  156. }
  157. if args.TaskNotify {
  158. s.WaitTaskNotify()
  159. }
  160. return nil
  161. })
  162. type DiskResizeOptions struct {
  163. DISK string `help:"ID or name of disk"`
  164. SIZE string `help:"Size of disk"`
  165. }
  166. R(&DiskResizeOptions{}, "disk-resize", "Resize a disk", func(s *mcclient.ClientSession, args *DiskResizeOptions) error {
  167. params := jsonutils.NewDict()
  168. params.Add(jsonutils.NewString(args.SIZE), "size")
  169. disk, err := modules.Disks.PerformAction(s, args.DISK, "resize", params)
  170. if err != nil {
  171. return err
  172. }
  173. printObject(disk)
  174. return nil
  175. })
  176. type DiskResetOptions struct {
  177. DISK string `help:"ID or name of disk"`
  178. SNAPSHOT string `help:"snapshots ID of disk"`
  179. AutoStart bool `help:"Autostart guest"`
  180. }
  181. R(&DiskResetOptions{}, "disk-reset", "Resize a disk", func(s *mcclient.ClientSession, args *DiskResetOptions) error {
  182. params := jsonutils.NewDict()
  183. params.Add(jsonutils.NewString(args.SNAPSHOT), "snapshot_id")
  184. if args.AutoStart {
  185. params.Add(jsonutils.JSONTrue, "auto_start")
  186. }
  187. disk, err := modules.Disks.PerformAction(s, args.DISK, "disk-reset", params)
  188. if err != nil {
  189. return err
  190. }
  191. printObject(disk)
  192. return nil
  193. })
  194. type DiskSaveOptions struct {
  195. ID string `help:"ID or name of the disk" json:"-"`
  196. NAME string `help:"Image name"`
  197. OSTYPE string `help:"Os type" choices:"Linux|Windows|VMware" json:"-"`
  198. Public *bool `help:"Make the image public available" json:"is_public"`
  199. Format string `help:"image format" choices:"vmdk|qcow2"`
  200. Notes string `help:"Notes about the image"`
  201. }
  202. R(&DiskSaveOptions{}, "disk-save", "Disk save image", func(s *mcclient.ClientSession, args *DiskSaveOptions) error {
  203. params, err := options.StructToParams(args)
  204. if err != nil {
  205. return err
  206. }
  207. params.Add(jsonutils.NewString(args.OSTYPE), "properties", "os_type")
  208. disk, err := modules.Disks.PerformAction(s, args.ID, "save", params)
  209. if err != nil {
  210. return err
  211. }
  212. printObject(disk)
  213. return nil
  214. })
  215. type DiskUpdateStatusOptions struct {
  216. ID string `help:"ID or name of disk"`
  217. STATUS string `help:"Disk status" choices:"ready"`
  218. }
  219. R(&DiskUpdateStatusOptions{}, "disk-update-status", "Set disk status", func(s *mcclient.ClientSession, args *DiskUpdateStatusOptions) error {
  220. params := jsonutils.NewDict()
  221. params.Add(jsonutils.NewString(args.STATUS), "status")
  222. disk, err := modules.Disks.PerformAction(s, args.ID, "status", params)
  223. if err != nil {
  224. return err
  225. }
  226. printObject(disk)
  227. return nil
  228. })
  229. type DiskChangeOwnerOptions struct {
  230. ID string `help:"Disk to change owner" json:"-"`
  231. PROJECT string `help:"Project ID or change" json:"tenant"`
  232. }
  233. R(&DiskChangeOwnerOptions{}, "disk-change-owner", "Change owner porject of a disk", func(s *mcclient.ClientSession, opts *DiskChangeOwnerOptions) error {
  234. params, err := options.StructToParams(opts)
  235. if err != nil {
  236. return err
  237. }
  238. srv, err := modules.Disks.PerformAction(s, opts.ID, "change-owner", params)
  239. if err != nil {
  240. return err
  241. }
  242. printObject(srv)
  243. return nil
  244. })
  245. R(&compute_options.DiskIdOptions{}, "disk-change-owner-candidate-domains", "Get change owner candidate domain list", func(s *mcclient.ClientSession, args *compute_options.DiskIdOptions) error {
  246. result, err := modules.Disks.GetSpecific(s, args.ID, "change-owner-candidate-domains", nil)
  247. if err != nil {
  248. return err
  249. }
  250. printObject(result)
  251. return nil
  252. })
  253. type DiskDownloadOptions struct {
  254. ID string `help:"ID or name of disk" json:"-"`
  255. Compress bool
  256. Sparse bool
  257. Timeout int `help:"Timeout hours for download" default:"5"`
  258. Debug bool
  259. FILE string
  260. }
  261. R(&DiskDownloadOptions{}, "disk-download", "Download disk from host", func(s *mcclient.ClientSession, args *DiskDownloadOptions) error {
  262. disk, err := modules.Disks.GetById(s, args.ID, nil)
  263. if err != nil {
  264. return err
  265. }
  266. storageId, _ := disk.GetString("storage_id")
  267. storage, err := modules.Storages.GetById(s, storageId, nil)
  268. if err != nil {
  269. return err
  270. }
  271. hostsInfo := []struct {
  272. Id string
  273. }{}
  274. storage.Unmarshal(&hostsInfo, "hosts")
  275. header := http.Header{}
  276. header.Set("X-Auth-Token", s.GetToken().GetTokenString())
  277. if args.Compress {
  278. header.Set("X-Compress-Content", "zlib")
  279. }
  280. if args.Sparse {
  281. header.Set("X-Sparse-Content", "true")
  282. }
  283. client := httputils.GetTimeoutClient(time.Hour * time.Duration(args.Timeout))
  284. for _, host := range hostsInfo {
  285. host, err := modules.Hosts.GetById(s, host.Id, nil)
  286. if err != nil {
  287. return err
  288. }
  289. managerUri, _ := host.GetString("manager_uri")
  290. if len(managerUri) == 0 {
  291. continue
  292. }
  293. url := fmt.Sprintf("%s/download/disks/%s/%s", managerUri, storageId, args.ID)
  294. resp, err := httputils.Request(client, context.Background(), httputils.GET, url, header, nil, args.Debug)
  295. if err != nil {
  296. log.Errorf("request %s error: %v", url, err)
  297. continue
  298. }
  299. defer resp.Body.Close()
  300. totalSize, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
  301. sparseHeader, _ := strconv.ParseInt(resp.Header.Get("X-Sparse-Header"), 10, 64)
  302. fi, err := os.Create(args.FILE)
  303. if err != nil {
  304. return errors.Wrapf(err, "os.Create(%s)", args.FILE)
  305. }
  306. defer fi.Close()
  307. var reader = resp.Body
  308. if args.Compress {
  309. zlibRC, err := zlib.NewReader(resp.Body)
  310. if err != nil {
  311. return errors.Wrapf(err, "zlib.NewReader")
  312. }
  313. defer zlibRC.Close()
  314. reader = zlibRC
  315. }
  316. var writer io.Writer = fi
  317. if sparseHeader > 0 {
  318. writer = sparsefile.NewSparseFileWriter(fi, sparseHeader, totalSize)
  319. fileSize, _ := strconv.ParseInt(resp.Header.Get("X-File-Size"), 10, 64)
  320. if fileSize > 0 {
  321. err = fi.Truncate(fileSize)
  322. if err != nil {
  323. return errors.Wrapf(err, "failed truncate file")
  324. }
  325. }
  326. }
  327. bar := pb.Full.Start64(totalSize)
  328. barReader := bar.NewProxyReader(reader)
  329. _, err = io.Copy(writer, barReader)
  330. return err
  331. }
  332. return fmt.Errorf("no available download url")
  333. })
  334. type SparseHoleOptions struct {
  335. FILE string
  336. }
  337. R(&SparseHoleOptions{}, "sparse-file-hole", "Show sparse file holes", func(s *mcclient.ClientSession, args *SparseHoleOptions) error {
  338. fi, err := os.Open(args.FILE)
  339. if err != nil {
  340. return err
  341. }
  342. defer fi.Close()
  343. sp, err := sparsefile.NewSparseFileReader(fi)
  344. if err != nil {
  345. return err
  346. }
  347. holes := sp.GetHoles()
  348. printObject(jsonutils.Marshal(holes))
  349. return nil
  350. })
  351. }