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