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