image.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 zstack
  15. import (
  16. "context"
  17. "fmt"
  18. "io"
  19. "net/http"
  20. "net/url"
  21. "sort"
  22. "time"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/httputils"
  27. "yunion.io/x/pkg/util/imagetools"
  28. "yunion.io/x/pkg/util/multipart"
  29. api "yunion.io/x/cloudmux/pkg/apis/compute"
  30. "yunion.io/x/cloudmux/pkg/cloudprovider"
  31. "yunion.io/x/cloudmux/pkg/multicloud"
  32. )
  33. type SBackupStorageRef struct {
  34. BackupStorageUUID string `json:"backupStorageUuid"`
  35. CreateDate time.Time `json:"createDate"`
  36. ImageUUID string `json:"ImageUuid"`
  37. InstallPath string `json:"installPath"`
  38. LastOpDate time.Time `json:"lastOpDate"`
  39. Status string `json:"status"`
  40. }
  41. type SImage struct {
  42. multicloud.SImageBase
  43. ZStackTags
  44. storageCache *SStoragecache
  45. // normalized image info
  46. imgInfo *imagetools.ImageInfo
  47. BackupStorageRefs []SBackupStorageRef `json:"backupStorageRefs"`
  48. ActualSize int `json:"actualSize"`
  49. CreateDate time.Time `json:"createDate"`
  50. Description string `json:"description"`
  51. Format string `json:"format"`
  52. LastOpDate time.Time `json:"lastOpDate"`
  53. MD5Sum string `json:"md5sum"`
  54. MediaType string `json:"mediaType"`
  55. Name string `json:"name"`
  56. Platform string `json:"platform"`
  57. Size int `json:"size"`
  58. State string `json:"state"`
  59. Status string `json:"Ready"`
  60. System bool `json:"system"`
  61. Type string `json:"type"`
  62. URL string `json:"url"`
  63. UUID string `json:"uuid"`
  64. }
  65. func (image *SImage) GetMinRamSizeMb() int {
  66. return 0
  67. }
  68. func (image *SImage) GetId() string {
  69. return image.UUID
  70. }
  71. func (image *SImage) GetName() string {
  72. return image.Name
  73. }
  74. func (image *SImage) IsEmulated() bool {
  75. return false
  76. }
  77. func (image *SImage) Delete(ctx context.Context) error {
  78. return image.storageCache.region.DeleteImage(image.UUID)
  79. }
  80. func (image *SImage) GetGlobalId() string {
  81. return image.UUID
  82. }
  83. func (region *SRegion) DeleteImage(imageId string) error {
  84. err := region.client.delete("images", imageId, "")
  85. if err != nil {
  86. return err
  87. }
  88. params := map[string]interface{}{
  89. "expungeImage": jsonutils.NewDict(),
  90. }
  91. _, err = region.client.put("images", imageId, jsonutils.Marshal(params))
  92. return err
  93. }
  94. func (image *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache {
  95. return image.storageCache
  96. }
  97. func (image *SImage) GetStatus() string {
  98. if image.State != "Enabled" {
  99. return api.CACHED_IMAGE_STATUS_CACHING
  100. }
  101. switch image.Status {
  102. case "Ready":
  103. return api.CACHED_IMAGE_STATUS_ACTIVE
  104. case "Downloading":
  105. return api.CACHED_IMAGE_STATUS_CACHING
  106. case "Deleted":
  107. return api.CACHED_IMAGE_STATUS_DELETING
  108. default:
  109. log.Errorf("Unknown image status: %s", image.Status)
  110. return api.CACHED_IMAGE_STATUS_CACHE_FAILED
  111. }
  112. }
  113. func (image *SImage) GetImageStatus() string {
  114. switch image.Status {
  115. case "Ready":
  116. return cloudprovider.IMAGE_STATUS_ACTIVE
  117. case "Deleted":
  118. return cloudprovider.IMAGE_STATUS_DELETED
  119. default:
  120. return cloudprovider.IMAGE_STATUS_KILLED
  121. }
  122. }
  123. func (image *SImage) Refresh() error {
  124. new, err := image.storageCache.region.GetImage(image.UUID)
  125. if err != nil {
  126. return err
  127. }
  128. return jsonutils.Update(image, new)
  129. }
  130. func (image *SImage) GetImageType() cloudprovider.TImageType {
  131. return cloudprovider.ImageTypeSystem
  132. }
  133. func (image *SImage) GetSizeByte() int64 {
  134. return int64(image.Size)
  135. }
  136. func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo {
  137. if self.imgInfo == nil {
  138. imgInfo := imagetools.NormalizeImageInfo(self.URL, "", self.Platform, self.Platform, "")
  139. self.imgInfo = &imgInfo
  140. }
  141. return self.imgInfo
  142. }
  143. func (image *SImage) GetOsType() cloudprovider.TOsType {
  144. return cloudprovider.TOsType(image.getNormalizedImageInfo().OsType)
  145. }
  146. func (image *SImage) GetOsDist() string {
  147. return image.getNormalizedImageInfo().OsDistro
  148. }
  149. func (image *SImage) GetOsVersion() string {
  150. return image.getNormalizedImageInfo().OsVersion
  151. }
  152. func (image *SImage) GetOsArch() string {
  153. return image.getNormalizedImageInfo().OsArch
  154. }
  155. func (image *SImage) GetBios() cloudprovider.TBiosType {
  156. return cloudprovider.TBiosType(image.getNormalizedImageInfo().OsBios)
  157. }
  158. func (image *SImage) GetOsLang() string {
  159. return image.getNormalizedImageInfo().OsLang
  160. }
  161. func (image *SImage) GetFullOsName() string {
  162. return image.URL
  163. }
  164. func (image *SImage) GetMinOsDiskSizeGb() int {
  165. return image.Size / 1024 / 1024 / 1024
  166. }
  167. func (image *SImage) GetImageFormat() string {
  168. return image.Format
  169. }
  170. func (image *SImage) GetCreatedAt() time.Time {
  171. return image.CreateDate
  172. }
  173. func (region *SRegion) GetImage(imageId string) (*SImage, error) {
  174. image := &SImage{}
  175. err := region.client.getResource("images", imageId, image)
  176. if err != nil {
  177. return nil, errors.Wrapf(err, "region.GetImage")
  178. }
  179. return image, nil
  180. }
  181. func (region *SRegion) GetImages(zoneId string, imageId string) ([]SImage, error) {
  182. images := []SImage{}
  183. params := url.Values{}
  184. params.Add("q", "system=false")
  185. if len(zoneId) > 0 {
  186. params.Add("q", "backupStorage.zone.uuid="+zoneId)
  187. }
  188. if len(imageId) > 0 {
  189. params.Add("q", "uuid="+imageId)
  190. }
  191. if SkipEsxi {
  192. params.Add("q", "type!=vmware")
  193. }
  194. return images, region.client.listAll("images", params, &images)
  195. }
  196. func (region *SRegion) GetBackupStorageUUID(zondId string) ([]string, error) {
  197. imageServers, err := region.GetImageServers(zondId, "")
  198. if err != nil {
  199. return nil, err
  200. }
  201. if len(imageServers) == 0 {
  202. return nil, fmt.Errorf("failed to found any image servers")
  203. }
  204. servers := ImageServers(imageServers)
  205. sort.Sort(servers)
  206. return []string{servers[0].UUID}, nil
  207. }
  208. func (region *SRegion) CreateImage(zoneId string, imageName, format, osType, desc string, reader io.Reader, size int64, callback func(progress float32)) (*SImage, error) {
  209. backupStorageUUIDs, err := region.GetBackupStorageUUID(zoneId)
  210. if err != nil {
  211. return nil, err
  212. }
  213. platform := ""
  214. switch osType {
  215. case "linux":
  216. platform = "Linux"
  217. case "windows":
  218. platform = "Windows"
  219. default:
  220. platform = "Other"
  221. }
  222. parmas := map[string]interface{}{
  223. "params": map[string]interface{}{
  224. "name": imageName,
  225. "url": fmt.Sprintf("upload://%s", imageName),
  226. "description": desc,
  227. "mediaType": "RootVolumeTemplate",
  228. "system": false,
  229. "format": format,
  230. "platform": platform,
  231. "backupStorageUuids": backupStorageUUIDs,
  232. "systemTags": []string{"qemuga", "bootMode::Legacy"},
  233. },
  234. }
  235. if reader == nil {
  236. return nil, fmt.Errorf("invalid reader")
  237. }
  238. if size == 0 {
  239. return nil, fmt.Errorf("invalid image size")
  240. }
  241. body := multipart.NewReader(reader, "", imageName)
  242. image := &SImage{}
  243. err = region.client.create("images", jsonutils.Marshal(parmas), image)
  244. if err != nil {
  245. return nil, err
  246. }
  247. if len(image.BackupStorageRefs) < 0 {
  248. return nil, fmt.Errorf("no InstallPath reture")
  249. }
  250. header := http.Header{}
  251. header.Add("X-IMAGE-UUID", image.UUID)
  252. header.Add("X-IMAGE-SIZE", fmt.Sprintf("%d", size))
  253. header.Add("Content-Type", body.FormDataContentType())
  254. r := multicloud.NewProgress(size, 99, body, callback)
  255. resp, err := httputils.Request(httputils.GetTimeoutClient(0), context.Background(), "POST", image.BackupStorageRefs[0].InstallPath, header, r, false)
  256. if err != nil {
  257. return nil, err
  258. }
  259. defer resp.Body.Close()
  260. return image, nil
  261. }