image.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 hcso
  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/cloudmux/pkg/apis"
  25. api "yunion.io/x/cloudmux/pkg/apis/compute"
  26. "yunion.io/x/cloudmux/pkg/cloudprovider"
  27. "yunion.io/x/cloudmux/pkg/multicloud"
  28. "yunion.io/x/cloudmux/pkg/multicloud/huawei"
  29. )
  30. type TImageOwnerType string
  31. const (
  32. ImageOwnerPublic TImageOwnerType = "gold" // 公共镜像:gold
  33. ImageOwnerSelf TImageOwnerType = "private" // 私有镜像:private
  34. ImageOwnerShared TImageOwnerType = "shared" // 共享镜像:shared
  35. EnvFusionCompute = "FusionCompute"
  36. EnvIronic = "Ironic"
  37. )
  38. const (
  39. ImageStatusQueued = "queued" // queued:表示镜像元数据已经创建成功,等待上传镜像文件。
  40. ImageStatusSaving = "saving" // saving:表示镜像正在上传文件到后端存储。
  41. ImageStatusDeleted = "deleted" // deleted:表示镜像已经删除。
  42. ImageStatusKilled = "killed" // killed:表示镜像上传错误。
  43. ImageStatusActive = "active" // active:表示镜像可以正常使用
  44. )
  45. // https://support.huaweicloud.com/api-ims/zh-cn_topic_0020091565.html
  46. type SImage struct {
  47. multicloud.SImageBase
  48. huawei.HuaweiTags
  49. storageCache *SStoragecache
  50. // normalized image info
  51. imgInfo *imagetools.ImageInfo
  52. Schema string `json:"schema"`
  53. MinDiskGB int64 `json:"min_disk"`
  54. CreatedAt time.Time `json:"created_at"`
  55. ImageSourceType string `json:"__image_source_type"`
  56. ContainerFormat string `json:"container_format"`
  57. File string `json:"file"`
  58. UpdatedAt time.Time `json:"updated_at"`
  59. Protected bool `json:"protected"`
  60. Checksum string `json:"checksum"`
  61. ID string `json:"id"`
  62. Isregistered string `json:"__isregistered"`
  63. MinRamMB int `json:"min_ram"`
  64. Lazyloading string `json:"__lazyloading"`
  65. Owner string `json:"owner"`
  66. OSType string `json:"__os_type"`
  67. Imagetype string `json:"__imagetype"`
  68. Visibility string `json:"visibility"`
  69. VirtualEnvType string `json:"virtual_env_type"`
  70. Platform string `json:"__platform"`
  71. SizeGB int `json:"size"`
  72. ImageSize int64 `json:"__image_size"`
  73. OSBit string `json:"__os_bit"`
  74. OSVersion string `json:"__os_version"`
  75. Name string `json:"name"`
  76. Self string `json:"self"`
  77. DiskFormat string `json:"disk_format"`
  78. Status string `json:"status"`
  79. SupportKVMFPGAType string `json:"__support_kvm_fpga_type"`
  80. SupportKVMNVMEHIGHIO string `json:"__support_nvme_highio"`
  81. SupportLargeMemory string `json:"__support_largememory"`
  82. SupportDiskIntensive string `json:"__support_diskintensive"`
  83. SupportHighPerformance string `json:"__support_highperformance"`
  84. SupportXENGPUType string `json:"__support_xen_gpu_type"`
  85. SupportKVMGPUType string `json:"__support_kvm_gpu_type"`
  86. SupportGPUT4 string `json:"__support_gpu_t4"`
  87. SupportKVMAscend310 string `json:"__support_kvm_ascend_310"`
  88. SupportArm string `json:"__support_arm"`
  89. }
  90. func (self *SImage) GetMinRamSizeMb() int {
  91. return self.MinRamMB
  92. }
  93. func (self *SImage) GetId() string {
  94. return self.ID
  95. }
  96. func (self *SImage) GetName() string {
  97. return self.Name
  98. }
  99. func (self *SImage) GetGlobalId() string {
  100. return self.ID
  101. }
  102. func (self *SImage) GetStatus() string {
  103. switch self.Status {
  104. case ImageStatusQueued:
  105. return api.CACHED_IMAGE_STATUS_CACHING
  106. case ImageStatusActive:
  107. return api.CACHED_IMAGE_STATUS_ACTIVE
  108. case ImageStatusKilled:
  109. return api.CACHED_IMAGE_STATUS_CACHE_FAILED
  110. default:
  111. return api.CACHED_IMAGE_STATUS_CACHE_FAILED
  112. }
  113. }
  114. func (self *SImage) GetImageStatus() string {
  115. switch self.Status {
  116. case ImageStatusQueued:
  117. return cloudprovider.IMAGE_STATUS_QUEUED
  118. case ImageStatusActive:
  119. return cloudprovider.IMAGE_STATUS_ACTIVE
  120. case ImageStatusKilled:
  121. return cloudprovider.IMAGE_STATUS_KILLED
  122. default:
  123. return cloudprovider.IMAGE_STATUS_KILLED
  124. }
  125. }
  126. func (self *SImage) Refresh() error {
  127. new, err := self.storageCache.region.GetImage(self.GetId())
  128. if err != nil {
  129. return err
  130. }
  131. return jsonutils.Update(self, new)
  132. }
  133. func (self *SImage) GetImageType() cloudprovider.TImageType {
  134. switch self.Imagetype {
  135. case "gold":
  136. return cloudprovider.ImageTypeSystem
  137. case "private":
  138. return cloudprovider.ImageTypeCustomized
  139. case "shared":
  140. return cloudprovider.ImageTypeShared
  141. default:
  142. return cloudprovider.ImageTypeCustomized
  143. }
  144. }
  145. func (self *SImage) GetSizeByte() int64 {
  146. return int64(self.MinDiskGB) * 1024 * 1024 * 1024
  147. }
  148. func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo {
  149. if self.imgInfo == nil {
  150. arch := "x86"
  151. if strings.ToLower(self.SupportArm) == "true" {
  152. arch = "arm"
  153. }
  154. imgInfo := imagetools.NormalizeImageInfo(self.ImageSourceType, arch, self.OSType, self.Platform, "")
  155. self.imgInfo = &imgInfo
  156. }
  157. return self.imgInfo
  158. }
  159. func (self *SImage) GetFullOsName() string {
  160. return self.ImageSourceType
  161. }
  162. func (self *SImage) GetOsType() cloudprovider.TOsType {
  163. return cloudprovider.TOsType(self.getNormalizedImageInfo().OsType)
  164. }
  165. func (self *SImage) GetOsDist() string {
  166. return self.getNormalizedImageInfo().OsDistro
  167. }
  168. func (self *SImage) GetOsVersion() string {
  169. return self.getNormalizedImageInfo().OsVersion
  170. }
  171. func (self *SImage) GetOsLang() string {
  172. return self.getNormalizedImageInfo().OsLang
  173. }
  174. func (self *SImage) GetOsArch() string {
  175. return self.getNormalizedImageInfo().OsArch
  176. }
  177. func (i *SImage) GetBios() cloudprovider.TBiosType {
  178. return cloudprovider.ToBiosType(i.getNormalizedImageInfo().OsBios)
  179. }
  180. func (self *SImage) GetMinOsDiskSizeGb() int {
  181. return int(self.MinDiskGB)
  182. }
  183. func (self *SImage) GetImageFormat() string {
  184. return self.DiskFormat
  185. }
  186. func (self *SImage) GetCreatedAt() time.Time {
  187. return self.CreatedAt
  188. }
  189. func (self *SImage) IsEmulated() bool {
  190. return false
  191. }
  192. func (self *SImage) Delete(ctx context.Context) error {
  193. return self.storageCache.region.DeleteImage(self.GetId())
  194. }
  195. func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache {
  196. return self.storageCache
  197. }
  198. func (self *SRegion) GetImage(imageId string) (*SImage, error) {
  199. image := &SImage{}
  200. err := DoGet(self.ecsClient.Images.Get, imageId, nil, image)
  201. if err != nil {
  202. return nil, errors.Wrap(err, "DoGet")
  203. }
  204. return image, nil
  205. }
  206. func excludeImage(image SImage) bool {
  207. if image.VirtualEnvType == "Ironic" {
  208. return true
  209. }
  210. if len(image.SupportDiskIntensive) > 0 {
  211. return true
  212. }
  213. if len(image.SupportKVMFPGAType) > 0 || len(image.SupportKVMAscend310) > 0 {
  214. return true
  215. }
  216. if len(image.SupportKVMGPUType) > 0 {
  217. return true
  218. }
  219. if len(image.SupportKVMNVMEHIGHIO) > 0 {
  220. return true
  221. }
  222. if len(image.SupportGPUT4) > 0 {
  223. return true
  224. }
  225. if len(image.SupportXENGPUType) > 0 {
  226. return true
  227. }
  228. if len(image.SupportHighPerformance) > 0 {
  229. return true
  230. }
  231. return false
  232. }
  233. // https://support.huaweicloud.com/api-ims/zh-cn_topic_0060804959.html
  234. func (self *SRegion) GetImages(status string, imagetype TImageOwnerType, name string, envType string) ([]SImage, error) {
  235. queries := map[string]string{}
  236. if len(status) > 0 {
  237. queries["status"] = status
  238. }
  239. if len(imagetype) > 0 {
  240. queries["__imagetype"] = string(imagetype)
  241. if imagetype == ImageOwnerPublic {
  242. queries["protected"] = "True"
  243. }
  244. }
  245. if len(envType) > 0 {
  246. queries["virtual_env_type"] = envType
  247. }
  248. if len(name) > 0 {
  249. queries["name"] = name
  250. }
  251. images := make([]SImage, 0)
  252. err := doListAllWithMarker(self.ecsClient.Images.List, queries, &images)
  253. // 排除掉需要特定镜像才能创建的实例类型
  254. // https://support.huaweicloud.com/eu-west-0-api-ims/zh-cn_topic_0031617666.html#ZH-CN_TOPIC_0031617666__table48545918250
  255. // https://support.huaweicloud.com/productdesc-ecs/zh-cn_topic_0088142947.html
  256. filtedImages := make([]SImage, 0)
  257. for i := range images {
  258. if !excludeImage(images[i]) {
  259. filtedImages = append(filtedImages, images[i])
  260. }
  261. }
  262. return filtedImages, err
  263. }
  264. func (self *SRegion) DeleteImage(imageId string) error {
  265. return DoDelete(self.ecsClient.OpenStackImages.Delete, imageId, nil, nil)
  266. }
  267. func (self *SRegion) GetImageByName(name string) (*SImage, error) {
  268. if len(name) == 0 {
  269. return nil, fmt.Errorf("image name should not be empty")
  270. }
  271. images, err := self.GetImages("", TImageOwnerType(""), name, "")
  272. if err != nil {
  273. return nil, err
  274. }
  275. if len(images) == 0 {
  276. return nil, cloudprovider.ErrNotFound
  277. }
  278. log.Debugf("%d image found match name %s", len(images), name)
  279. return &images[0], nil
  280. }
  281. /*
  282. https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html
  283. os version 取值范围: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html
  284. 用于创建私有镜像的源云服务器系统盘大小大于等于40GB且不超过1024GB。
  285. 目前支持vhd,zvhd、raw,qcow2
  286. todo: 考虑使用镜像快速导入。 https://support.huaweicloud.com/api-ims/zh-cn_topic_0133188204.html
  287. 使用OBS文件创建镜像
  288. * openstack原生接口支持的格式:https://support.huaweicloud.com/api-ims/zh-cn_topic_0031615566.html
  289. */
  290. func (self *SRegion) ImportImageJob(name string, osDist string, osVersion string, osArch string, bucket string, key string, minDiskGB int64) (string, error) {
  291. os_version, err := stdVersion(osDist, osVersion, osArch)
  292. log.Debugf("%s %s %s: %s.min_disk %d GB", osDist, osVersion, osArch, os_version, minDiskGB)
  293. if err != nil {
  294. log.Debugln(err)
  295. }
  296. params := jsonutils.NewDict()
  297. params.Add(jsonutils.NewString(name), "name")
  298. image_url := fmt.Sprintf("%s:%s", bucket, key)
  299. params.Add(jsonutils.NewString(image_url), "image_url")
  300. if len(os_version) > 0 {
  301. params.Add(jsonutils.NewString(os_version), "os_version")
  302. }
  303. params.Add(jsonutils.NewBool(true), "is_config_init")
  304. params.Add(jsonutils.NewBool(true), "is_config")
  305. params.Add(jsonutils.NewInt(minDiskGB), "min_disk")
  306. ret, err := self.ecsClient.Images.PerformAction2("action", "", params, "")
  307. if err != nil {
  308. return "", err
  309. }
  310. return ret.GetString("job_id")
  311. }
  312. func formatVersion(osDist string, osVersion string) (string, error) {
  313. err := fmt.Errorf("unsupport version %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osVersion)
  314. dist := strings.ToLower(osDist)
  315. if dist == "ubuntu" || dist == "redhat" || dist == "centos" || dist == "oracle" || dist == "euleros" {
  316. parts := strings.Split(osVersion, ".")
  317. if len(parts) < 2 {
  318. return "", err
  319. }
  320. return parts[0] + "." + parts[1], nil
  321. }
  322. if dist == "debian" {
  323. parts := strings.Split(osVersion, ".")
  324. if len(parts) < 3 {
  325. return "", err
  326. }
  327. return parts[0] + "." + parts[1] + "." + parts[2], nil
  328. }
  329. if dist == "fedora" || dist == "windows" || dist == "suse" {
  330. parts := strings.Split(osVersion, ".")
  331. if len(parts) < 1 {
  332. return "", err
  333. }
  334. return parts[0], nil
  335. }
  336. if dist == "opensuse" {
  337. parts := strings.Split(osVersion, ".")
  338. if len(parts) == 0 {
  339. return "", err
  340. }
  341. if len(parts) == 1 {
  342. return parts[0], nil
  343. }
  344. if len(parts) >= 2 {
  345. return parts[0] + "." + parts[1], nil
  346. }
  347. }
  348. return "", err
  349. }
  350. // https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html
  351. func stdVersion(osDist string, osVersion string, osArch string) (string, error) {
  352. // 架构
  353. arch := ""
  354. switch osArch {
  355. case "64", apis.OS_ARCH_X86_64, apis.OS_ARCH_AARCH64, apis.OS_ARCH_ARM:
  356. arch = "64bit"
  357. case "32", apis.OS_ARCH_X86_32, apis.OS_ARCH_AARCH32:
  358. arch = "32bit"
  359. default:
  360. return "", fmt.Errorf("unsupported arch %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osArch)
  361. }
  362. _dist := strings.Split(strings.TrimSpace(osDist), " ")[0]
  363. _dist = strings.ToLower(_dist)
  364. // 版本
  365. ver, err := formatVersion(_dist, osVersion)
  366. if err != nil {
  367. return "", err
  368. }
  369. // 操作系统
  370. dist := ""
  371. switch _dist {
  372. case "ubuntu":
  373. return fmt.Sprintf("Ubuntu %s server %s", ver, arch), nil
  374. case "redhat":
  375. dist = "Redhat Linux Enterprise"
  376. case "centos":
  377. dist = "CentOS"
  378. case "fedora":
  379. dist = "Fedora"
  380. case "debian":
  381. dist = "Debian GNU/Linux"
  382. case "windows":
  383. dist = "Windows Server"
  384. case "oracle":
  385. dist = "Oracle Linux Server release"
  386. case "suse":
  387. dist = "SUSE Linux Enterprise Server"
  388. case "opensuse":
  389. dist = "OpenSUSE"
  390. case "euleros":
  391. dist = "EulerOS"
  392. default:
  393. return "", fmt.Errorf("unsupported os %s. reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", dist)
  394. }
  395. return fmt.Sprintf("%s %s %s", dist, ver, arch), nil
  396. }