image.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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 aws
  15. import (
  16. "context"
  17. "fmt"
  18. "strings"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/util/imagetools"
  24. "yunion.io/x/pkg/utils"
  25. api "yunion.io/x/cloudmux/pkg/apis/compute"
  26. "yunion.io/x/cloudmux/pkg/cloudprovider"
  27. "yunion.io/x/cloudmux/pkg/multicloud"
  28. )
  29. type ImageStatusType string
  30. const (
  31. ImageStatusCreating ImageStatusType = "pending"
  32. ImageStatusAvailable ImageStatusType = "available"
  33. ImageStatusCreateFailed ImageStatusType = "failed"
  34. ImageImportStatusCompleted = "completed"
  35. ImageImportStatusUncompleted = "uncompleted"
  36. ImageImportStatusError = "error"
  37. ImageImportStatusDeleted = "deleted"
  38. )
  39. type TImageOwnerType string
  40. const (
  41. ImageOwnerTypeSystem = TImageOwnerType("system")
  42. ImageOwnerTypeSelf = TImageOwnerType("self")
  43. ImageOwnerTypeOther = TImageOwnerType("other")
  44. )
  45. var (
  46. ImageOwnerAll = []TImageOwnerType(nil)
  47. ImageOwnerSelf = []TImageOwnerType{ImageOwnerTypeSelf}
  48. ImageOwnerSystem = []TImageOwnerType{ImageOwnerTypeSystem}
  49. ImageOwnerSelfSystem = []TImageOwnerType{ImageOwnerTypeSystem, ImageOwnerTypeSelf}
  50. )
  51. type ImageImportTask struct {
  52. multicloud.SResourceBase
  53. region *SRegion
  54. ImageId string `xml:"imageId"`
  55. TaskId string `xml:"importTaskId"`
  56. Progress string `xml:"progress"`
  57. Status string `xml:"status"`
  58. }
  59. type RootDevice struct {
  60. SnapshotId string
  61. Size int // GB
  62. Category string // VolumeType
  63. }
  64. type SImage struct {
  65. multicloud.SImageBase
  66. AwsTags
  67. storageCache *SStoragecache
  68. // normalized image info
  69. imgInfo *imagetools.ImageInfo
  70. Architecture string `xml:"architecture"`
  71. CreationTime time.Time `xml:"creationDate"`
  72. Description string `xml:"description"`
  73. ImageId string `xml:"imageId"`
  74. ImageName string `xml:"name"`
  75. ImageType string `xml:"imageType"`
  76. EnaSupport bool `xml:"enaSupport"`
  77. Platform string `xml:"platformDetails"`
  78. Status ImageStatusType `xml:"imageState"`
  79. OwnerType string `xml:"imageOwnerAlias"`
  80. RootDeviceName string `xml:"rootDeviceName"`
  81. BlockDeviceMapping []struct {
  82. DeviceName string `xml:"deviceName"`
  83. Ebs struct {
  84. SnapshotId string `xml:"snapshotId"`
  85. VolumeSize int `xml:"volumeSize"`
  86. DeleteOnTermination bool `xml:"deleteOnTermination"`
  87. VolumeType string `xml:"volumeType"`
  88. Iops int `xml:"iops"`
  89. Encrypted bool `xml:"encrypted"`
  90. } `xml:"ebs"`
  91. } `xml:"blockDeviceMapping>item"`
  92. Public bool `xml:"isPublic"`
  93. Hypervisor string `xml:"hypervisor"`
  94. VirtualizationType string `xml:"virtualizationType"`
  95. OwnerId string `xml:"imageOwnerId"`
  96. }
  97. func (self *ImageImportTask) GetId() string {
  98. return self.TaskId
  99. }
  100. func (self *ImageImportTask) GetName() string {
  101. return self.GetId()
  102. }
  103. func (self *ImageImportTask) GetGlobalId() string {
  104. return self.GetId()
  105. }
  106. func (self *ImageImportTask) Refresh() error {
  107. task, err := self.region.GetImportImageTask(self.TaskId)
  108. if err != nil {
  109. return errors.Wrapf(err, "GetImportImageTask")
  110. }
  111. log.Debugf("DescribeImportImage Task %s", jsonutils.Marshal(task))
  112. return jsonutils.Update(self, task)
  113. }
  114. func (self *SRegion) GetImportImageTasks(ids []string) ([]ImageImportTask, error) {
  115. params := map[string]string{}
  116. for i, id := range ids {
  117. params[fmt.Sprintf("ImportTaskId.%d", i+1)] = id
  118. }
  119. ret := struct {
  120. ImportImageTaskSet []ImageImportTask `xml:"importImageTaskSet>item"`
  121. }{}
  122. err := self.ec2Request("DescribeImportImageTasks", params, &ret)
  123. if err != nil {
  124. return nil, errors.Wrap(err, "DescribeImportImageTasks")
  125. }
  126. return ret.ImportImageTaskSet, nil
  127. }
  128. func (self *SRegion) GetImportImageTask(id string) (*ImageImportTask, error) {
  129. tasks, err := self.GetImportImageTasks([]string{id})
  130. if err != nil {
  131. return nil, err
  132. }
  133. for i := range tasks {
  134. if tasks[i].TaskId == id {
  135. return &tasks[i], nil
  136. }
  137. }
  138. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", id)
  139. }
  140. func (self *ImageImportTask) IsEmulated() bool {
  141. return true
  142. }
  143. func (self *ImageImportTask) GetStatus() string {
  144. self.Refresh()
  145. if self.Status == "completed" {
  146. return ImageImportStatusCompleted
  147. } else if self.Status == "deleted" {
  148. return ImageImportStatusDeleted
  149. } else {
  150. return ImageImportStatusUncompleted
  151. }
  152. }
  153. func (self *SImage) GetMinRamSizeMb() int {
  154. return 0
  155. }
  156. func (self *SImage) GetId() string {
  157. return self.ImageId
  158. }
  159. func (self *SImage) GetName() string {
  160. if len(self.ImageName) > 0 {
  161. return self.ImageName
  162. }
  163. return self.GetId()
  164. }
  165. func (self *SImage) GetGlobalId() string {
  166. return self.ImageId
  167. }
  168. func (self *SImage) GetStatus() string {
  169. switch self.Status {
  170. case ImageStatusCreating:
  171. return api.CACHED_IMAGE_STATUS_CACHING
  172. case ImageStatusAvailable:
  173. return api.CACHED_IMAGE_STATUS_ACTIVE
  174. case ImageStatusCreateFailed:
  175. return api.CACHED_IMAGE_STATUS_CACHE_FAILED
  176. default:
  177. return api.CACHED_IMAGE_STATUS_CACHE_FAILED
  178. }
  179. }
  180. func (self *SImage) GetImageStatus() string {
  181. switch self.Status {
  182. case ImageStatusCreating:
  183. return cloudprovider.IMAGE_STATUS_QUEUED
  184. case ImageStatusAvailable:
  185. return cloudprovider.IMAGE_STATUS_ACTIVE
  186. case ImageStatusCreateFailed:
  187. return cloudprovider.IMAGE_STATUS_KILLED
  188. default:
  189. return cloudprovider.IMAGE_STATUS_KILLED
  190. }
  191. }
  192. func (self *SImage) Refresh() error {
  193. new, err := self.storageCache.region.GetImage(self.ImageId)
  194. if err != nil {
  195. return err
  196. }
  197. return jsonutils.Update(self, new)
  198. }
  199. func (self *SImage) GetImageType() cloudprovider.TImageType {
  200. return getImageType(self)
  201. }
  202. func (self *SImage) GetBlockDeviceNames() []string {
  203. ret := []string{}
  204. for _, dev := range self.BlockDeviceMapping {
  205. ret = append(ret, dev.DeviceName)
  206. if strings.HasPrefix(dev.DeviceName, "/dev/xvd") && !utils.IsInStringArray("/dev/sda", ret) { // 系统盘是/dev/xvda, 则不能指定devName 为 /dev/sda
  207. ret = append(ret, "/dev/sda")
  208. }
  209. }
  210. return ret
  211. }
  212. func (self *SImage) GetSizeByte() int64 {
  213. return int64(self.GetMinOsDiskSizeGb()) * 1024 * 1024 * 1024
  214. }
  215. func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo {
  216. if self.imgInfo == nil {
  217. name := self.ImageName
  218. if len(self.Description) > 0 && self.GetImageType() != cloudprovider.ImageTypeCustomized {
  219. name = self.Description
  220. }
  221. imgInfo := imagetools.NormalizeImageInfo(name, self.Architecture, getImageOSType(*self), getImageOSDist(*self), getImageOSVersion(*self))
  222. self.imgInfo = &imgInfo
  223. }
  224. return self.imgInfo
  225. }
  226. func (self *SImage) GetOsType() cloudprovider.TOsType {
  227. return cloudprovider.TOsType(self.getNormalizedImageInfo().OsType)
  228. }
  229. func (self *SImage) GetOsArch() string {
  230. return self.getNormalizedImageInfo().OsArch
  231. }
  232. func (self *SImage) GetOsDist() string {
  233. return self.getNormalizedImageInfo().OsDistro
  234. }
  235. func (self *SImage) GetOsVersion() string {
  236. return self.getNormalizedImageInfo().OsVersion
  237. }
  238. func (self *SImage) GetOsLang() string {
  239. return self.getNormalizedImageInfo().OsLang
  240. }
  241. func (self *SImage) GetBios() cloudprovider.TBiosType {
  242. return cloudprovider.ToBiosType(self.getNormalizedImageInfo().OsBios)
  243. }
  244. func (self *SImage) GetFullOsName() string {
  245. return self.getNormalizedImageInfo().GetFullOsName()
  246. }
  247. func (self *SImage) GetMinOsDiskSizeGb() int {
  248. for _, dev := range self.BlockDeviceMapping {
  249. if dev.DeviceName == self.RootDeviceName {
  250. return dev.Ebs.VolumeSize
  251. }
  252. }
  253. return 0
  254. }
  255. func (self *SImage) GetImageFormat() string {
  256. return "vhd"
  257. }
  258. func (self *SImage) GetCreatedAt() time.Time {
  259. return self.CreationTime
  260. }
  261. func (self *SImage) IsEmulated() bool {
  262. return false
  263. }
  264. func (self *SImage) Delete(ctx context.Context) error {
  265. // todo: implement me
  266. return self.storageCache.region.DeleteImage(self.ImageId)
  267. }
  268. func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache {
  269. return self.storageCache
  270. }
  271. func (self *SRegion) ImportImage(name string, osArch string, osType string, osDist string, diskFormat string, bucket string, key string) (*ImageImportTask, error) {
  272. params := map[string]string{
  273. "Architecture": osArch,
  274. "Hypervisor": "xen",
  275. "Platform": osType,
  276. "RoleName": "vmimport",
  277. "TagSpecification.1.ResourceType": "import-image-task",
  278. "TagSpecification.1.Tag.1.Key": "Name",
  279. "TagSpecification.1.Tag.1.Value": name,
  280. "Description": fmt.Sprintf("vmimport %s - %s", name, osDist),
  281. "DiskContainer.1.Format": strings.ToUpper(diskFormat),
  282. "DiskContainer.1.DeviceName": "/dev/sda",
  283. "DiskContainer.1.Url": fmt.Sprintf("s3://%s/%s", bucket, key),
  284. "LicenseType": "BYOL",
  285. }
  286. ret := &ImageImportTask{region: self}
  287. err := self.ec2Request("ImportImage", params, ret)
  288. if err != nil {
  289. return nil, errors.Wrapf(err, "ImportImage")
  290. }
  291. return ret, nil
  292. }
  293. type ImageExportTask struct {
  294. ImageId string
  295. RegionId string
  296. TaskId string `xml:"exportTaskId"`
  297. }
  298. func (self *SRegion) ExportImage(instanceId string, imageId string) (*ImageExportTask, error) {
  299. params := map[string]string{
  300. "InstanceId": instanceId,
  301. "TargetEnvironment": "vmware",
  302. "Description": fmt.Sprintf("image %s export from aws", imageId),
  303. "ExportToS3.ContainerFormat": "ova",
  304. "ExportToS3.DiskImageFormat": "RAW",
  305. "ExportToS3.S3Bucket": "imgcache-onecloud",
  306. }
  307. ret := struct {
  308. ExportTask ImageExportTask `xml:"exportTask"`
  309. }{}
  310. err := self.ec2Request("CreateInstanceExportTask", params, ret)
  311. if err != nil {
  312. return nil, errors.Wrapf(err, "CreateInstanceExportTask")
  313. }
  314. ret.ExportTask.RegionId = self.RegionId
  315. ret.ExportTask.ImageId = imageId
  316. return &ret.ExportTask, nil
  317. }
  318. func (self *SRegion) GetImage(imageId string) (*SImage, error) {
  319. if len(imageId) == 0 {
  320. return nil, fmt.Errorf("GetImage image id should not be empty")
  321. }
  322. images, err := self.getImages("", ImageOwnerAll, []string{imageId}, "", "", nil, "")
  323. if err != nil {
  324. return nil, errors.Wrap(err, "getImages")
  325. }
  326. if len(images) == 0 {
  327. return nil, errors.Wrap(cloudprovider.ErrNotFound, "getImages")
  328. }
  329. return &images[0], nil
  330. }
  331. func (self *SRegion) GetImageByName(name string, owners []TImageOwnerType) (*SImage, error) {
  332. if len(name) == 0 {
  333. return nil, fmt.Errorf("image name should not be empty")
  334. }
  335. images, err := self.getImages("", owners, nil, name, "hvm", nil, "")
  336. if err != nil {
  337. return nil, errors.Wrap(err, "getImages")
  338. }
  339. if len(images) == 0 {
  340. return nil, errors.Wrap(cloudprovider.ErrNotFound, "getImages")
  341. }
  342. log.Debugf("%d image found match name %s", len(images), name)
  343. return &images[0], nil
  344. }
  345. func (self *SRegion) GetImageStatus(imageId string) (ImageStatusType, error) {
  346. image, err := self.GetImage(imageId)
  347. if err != nil {
  348. return "", err
  349. }
  350. return image.Status, nil
  351. }
  352. func getLatestImage(images []SImage) SImage {
  353. var latestBuild string
  354. latestBuildIdx := -1
  355. for i := range images {
  356. if latestBuildIdx < 0 || comapreImageBuildIds(latestBuild, images[i]) < 0 {
  357. latestBuild = getImageOSBuildID(images[i])
  358. latestBuildIdx = i
  359. }
  360. }
  361. return images[latestBuildIdx]
  362. }
  363. func (self *SRegion) GetImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string, latest bool) ([]SImage, error) {
  364. images, err := self.getImages(status, owners, imageId, name, virtualizationType, ownerIds, volumeType)
  365. if err != nil {
  366. return nil, errors.Wrap(err, "getImages")
  367. }
  368. if !latest {
  369. return images, err
  370. }
  371. noVersionImages := make([]SImage, 0)
  372. versionedImages := make(map[string][]SImage)
  373. for i := range images {
  374. key := fmt.Sprintf("%s%s%s", getImageOSDist(images[i]), getImageOSVersion(images[i]), images[i].Architecture)
  375. if len(key) == 0 {
  376. noVersionImages = append(noVersionImages, images[i])
  377. continue
  378. }
  379. if _, ok := versionedImages[key]; !ok {
  380. versionedImages[key] = make([]SImage, 0)
  381. }
  382. versionedImages[key] = append(versionedImages[key], images[i])
  383. }
  384. for key := range versionedImages {
  385. noVersionImages = append(noVersionImages, getLatestImage(versionedImages[key]))
  386. }
  387. return noVersionImages, nil
  388. }
  389. func (self *SRegion) getImages(status ImageStatusType, owners []TImageOwnerType, imageId []string, name string, virtualizationType string, ownerIds []string, volumeType string) ([]SImage, error) {
  390. params := map[string]string{}
  391. idx := 1
  392. if len(status) > 0 {
  393. params[fmt.Sprintf("Filter.%d.Name", idx)] = "state"
  394. params[fmt.Sprintf("Filter.%d.Value.1", idx)] = string(status)
  395. idx++
  396. }
  397. if len(name) > 0 {
  398. params[fmt.Sprintf("Filter.%d.Name", idx)] = "name"
  399. params[fmt.Sprintf("Filter.%d.Value.1", idx)] = name
  400. idx++
  401. }
  402. if len(virtualizationType) > 0 {
  403. params[fmt.Sprintf("Filter.%d.Name", idx)] = "virtualization-type"
  404. params[fmt.Sprintf("Filter.%d.Value.1", idx)] = virtualizationType
  405. idx++
  406. }
  407. if len(volumeType) > 0 {
  408. params[fmt.Sprintf("Filter.%d.Name", idx)] = "block-device-mapping.volume-type"
  409. params[fmt.Sprintf("Filter.%d.Value.1", idx)] = volumeType
  410. idx++
  411. }
  412. if len(owners) > 0 || len(ownerIds) > 0 {
  413. for i, owner := range imageOwnerTypes2Strings(owners, ownerIds) {
  414. params[fmt.Sprintf("Owner.%d", i+1)] = string(owner)
  415. }
  416. }
  417. for i, id := range imageId {
  418. params[fmt.Sprintf("ImageId.%d", i+1)] = id
  419. }
  420. params[fmt.Sprintf("Filter.%d.Name", idx)] = "image-type"
  421. params[fmt.Sprintf("Filter.%d.Value.1", idx)] = "machine"
  422. idx++
  423. ret := []SImage{}
  424. for {
  425. part := struct {
  426. ImagesSet []SImage `xml:"imagesSet>item"`
  427. NextToken string `xml:"nextToken"`
  428. }{}
  429. err := self.ec2Request("DescribeImages", params, &part)
  430. if err != nil {
  431. return nil, errors.Wrapf(err, "DescribeImages")
  432. }
  433. ret = append(ret, part.ImagesSet...)
  434. if len(part.ImagesSet) == 0 || len(part.NextToken) == 0 {
  435. break
  436. }
  437. params["NextToken"] = part.NextToken
  438. }
  439. return ret, nil
  440. }
  441. func (self *SRegion) DeleteImage(imageId string) error {
  442. params := map[string]string{"ImageId": imageId}
  443. ret := struct{}{}
  444. return self.ec2Request("DeregisterImage", params, &ret)
  445. }