yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/google/sku.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 google 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 "yunion.io/x/log" 23 "yunion.io/x/pkg/errors" 24 "yunion.io/x/pkg/utils" 25 26 api "yunion.io/x/cloudmux/pkg/apis/compute" 27 ) 28 29 type SkuBillingCatetory struct { 30 ServiceDisplayName string 31 ResourceFamily string 32 ResourceGroup string 33 UsageType string 34 } 35 36 type SkuPricingInfo struct { 37 Summary string 38 PricingExpression SPricingExpression 39 currencyConversionRate int 40 EffectiveTime time.Time 41 } 42 43 type SPricingExpression struct { 44 UsageUnit string 45 UsageUnitDescription string 46 BaseUnit string 47 BaseUnitDescription string 48 BaseUnitConversionFactor string 49 DisplayQuantity int 50 TieredRates []STieredRate 51 } 52 53 type STieredRate struct { 54 StartUsageAmount int 55 UnitPrice SUnitPrice 56 } 57 58 type SUnitPrice struct { 59 CurrencyCode string 60 Units string 61 Nanos int 62 } 63 64 type SSkuBilling struct { 65 Name string 66 SkuId string 67 Description string 68 Category SkuBillingCatetory 69 ServiceRegions []string 70 PricingInfo []SkuPricingInfo 71 ServiceProviderName string 72 } 73 74 func (region *SRegion) ListSkuBilling(pageSize int, pageToken string) ([]SSkuBilling, error) { 75 skus := []SSkuBilling{} 76 params := map[string]string{} 77 err := region.BillingList("services/6F81-5844-456A/skus", params, pageSize, pageToken, &skus) 78 if err != nil { 79 return nil, err 80 } 81 return skus, nil 82 } 83 84 type SRateInfo struct { 85 // region: europe-north1 86 // family: Compute, Storage, Network 87 // resource: CPU, Ram, Gpu, N1Standard 88 // category: custome, predefine, optimized 89 // startUsageAmount: 90 // map[region]map[family]map[resource]map[category]map[startUsageAmount][money] 91 92 Info map[string]map[string]map[string]map[string]map[string]float64 93 } 94 95 func (region *SRegion) GetSkuRateInfo(skus []SSkuBilling) SRateInfo { 96 result := SRateInfo{ 97 Info: map[string]map[string]map[string]map[string]map[string]float64{}, 98 } 99 for _, sku := range skus { 100 if sku.ServiceProviderName == "Google" && 101 sku.Category.ServiceDisplayName == "Compute Engine" && 102 utils.IsInStringArray(sku.Category.ResourceFamily, []string{"Compute", "Storage"}) && 103 sku.Category.UsageType == "OnDemand" { 104 for _, region := range sku.ServiceRegions { 105 if _, ok := result.Info[region]; !ok { 106 result.Info[region] = map[string]map[string]map[string]map[string]float64{} 107 } 108 if _, ok := result.Info[region][sku.Category.ResourceFamily]; !ok { 109 result.Info[region][sku.Category.ResourceFamily] = map[string]map[string]map[string]float64{} 110 } 111 if sku.Category.ResourceGroup == "N1Standard" { 112 if strings.Index(sku.Description, "Core") > 0 { 113 sku.Category.ResourceGroup = "CPU" 114 } else if strings.Index(sku.Description, "Ram") > 0 { 115 sku.Category.ResourceGroup = "RAM" 116 } 117 } 118 if !utils.IsInStringArray(sku.Category.ResourceGroup, []string{"F1Micro", "G1Small", "CPU", "RAM", "PDStandard", "SSD", "LocalSSD", "f1-micro", "g1-small"}) { 119 continue 120 } 121 if utils.IsInStringArray(sku.Category.ResourceGroup, []string{"PDStandard", "SSD"}) && strings.Index(sku.Description, "Regional") >= 0 { 122 continue 123 } 124 convers := map[string]string{ 125 "PDStandard": api.STORAGE_GOOGLE_PD_STANDARD, 126 "SSD": api.STORAGE_GOOGLE_PD_SSD, 127 "LocalSSD": api.STORAGE_GOOGLE_LOCAL_SSD, 128 "F1Micro": "f1-micro", 129 "G1Small": "g1-small", 130 } 131 if group, ok := convers[sku.Category.ResourceGroup]; ok { 132 sku.Category.ResourceGroup = group 133 } 134 135 if _, ok := result.Info[region][sku.Category.ResourceFamily][sku.Category.ResourceGroup]; !ok { 136 result.Info[region][sku.Category.ResourceFamily][sku.Category.ResourceGroup] = map[string]map[string]float64{} 137 } 138 description := strings.ToLower(sku.Description) 139 if strings.Contains("sole", description) { //单租户 140 continue 141 } 142 category := "" 143 keys := []string{"memory optimized", "memory-optimized", "compute optimized", "n1 predefined", "n2 instance", "n2 custom extended", "custom extended", "n2 custom", "custom instance"} 144 categories := map[string]string{ 145 "memory optimized": "ultramem", 146 "memory-optimized": "memory-optimized", 147 "compute optimized": "compute-optimized", 148 149 "n1 predefined": "n1-predefined", 150 "n2 instance": "n2-instance", 151 152 "n2 custom extended": "n2-custom-extended", 153 "custom extended": "custom-extended", 154 155 "n2 custom": "n2-custom", //cpu ram 156 "custom instance": "custom-instance", //cpu 157 } 158 for _, key := range keys { 159 _category := categories[key] 160 if strings.Contains(description, key) { 161 category = _category 162 break 163 } 164 } 165 if utils.IsInStringArray(sku.Category.ResourceGroup, []string{api.STORAGE_GOOGLE_PD_STANDARD, api.STORAGE_GOOGLE_PD_SSD, api.STORAGE_GOOGLE_LOCAL_SSD, "f1-micro", "g1-small"}) { 166 category = sku.Category.ResourceGroup 167 } 168 if len(category) == 0 { 169 continue 170 } 171 if _, ok := result.Info[region][sku.Category.ResourceFamily][sku.Category.ResourceGroup][category]; !ok { 172 result.Info[region][sku.Category.ResourceFamily][sku.Category.ResourceGroup][category] = map[string]float64{} 173 } 174 for _, priceInfo := range sku.PricingInfo { 175 for _, price := range priceInfo.PricingExpression.TieredRates { 176 result.Info[region][sku.Category.ResourceFamily][sku.Category.ResourceGroup][category][fmt.Sprintf("%d", price.StartUsageAmount)] = float64(price.UnitPrice.Nanos) / 1000000000 177 } 178 } 179 } 180 } 181 } 182 return result 183 } 184 185 func (rate *SRateInfo) GetDiscount(sku string) []float64 { 186 if strings.Index(sku, "custom") < 0 && strings.HasPrefix(sku, "c") || strings.Index(sku, "n2") >= 0 { 187 return []float64{0, 0.15, 0.25, 0.4} 188 } 189 return []float64{0, 0.2, 0.4, 0.6} 190 } 191 192 func (rate *SRateInfo) GetSkuType(sku string) (string, error) { 193 if strings.Index(sku, "custom") >= 0 { 194 cpuType := "custom-instance" 195 if strings.HasPrefix(sku, "n2") { 196 cpuType = "n2-custom" 197 } 198 return cpuType, nil 199 } 200 if strings.HasPrefix(sku, "n1") { 201 return "n1-predefined", nil 202 } 203 if strings.HasPrefix(sku, "n2") { 204 return "n2-instance", nil 205 } 206 if strings.HasPrefix(sku, "c") { 207 return "compute-optimized", nil 208 } 209 if strings.HasPrefix(sku, "m") { 210 return "memory-optimized", nil 211 } 212 return "", fmt.Errorf("failed to found sku %s type", sku) 213 } 214 215 func (rate *SRateInfo) GetCpuPrice(regionId, cpuType string) (float64, error) { 216 computePrice, err := rate.GetComputePrice(regionId) 217 if err != nil { 218 return 0, errors.Wrap(err, "GetComputePrice") 219 } 220 221 _cpuPrice, ok := computePrice["CPU"] 222 if !ok { 223 return 0, fmt.Errorf("failed to found region %s compute cpu price info", regionId) 224 } 225 226 cpuPrice, ok := _cpuPrice[cpuType] 227 if !ok { 228 return 0, fmt.Errorf("failed to found region %s compute %s cpu price info", regionId, cpuType) 229 } 230 return cpuPrice["0"], nil 231 } 232 233 func (rate *SRateInfo) GetExtendMemoryGb(sku string, cpu int, memoryMb int) float64 { 234 maxMemoryGb := 0.0 235 if strings.Index(sku, "custom") >= 0 { 236 maxMemoryGb = float64(cpu) * 6.5 237 if strings.Index(sku, "n2") >= 0 { 238 maxMemoryGb = float64(cpu) * 8 239 } 240 } 241 if float64(memoryMb)/1024 > maxMemoryGb { 242 return float64(memoryMb)/1024 - maxMemoryGb 243 } 244 return 0.0 245 } 246 247 func (rate *SRateInfo) GetMemoryPrice(regionId string, memoryType string) (float64, error) { 248 computePrice, err := rate.GetComputePrice(regionId) 249 if err != nil { 250 return 0, errors.Wrap(err, "GetComputePrice") 251 } 252 253 _memoryPrice, ok := computePrice["RAM"] 254 if !ok { 255 return 0, fmt.Errorf("failed to found region %s compute memory price info", regionId) 256 } 257 258 memoryPrice, ok := _memoryPrice[memoryType] 259 if !ok { 260 return 0, fmt.Errorf("failed to found region %s compute %s memory price info", regionId, memoryType) 261 } 262 return memoryPrice["0"], nil 263 } 264 265 func (rate *SRateInfo) GetComputePrice(regionId string) (map[string]map[string]map[string]float64, error) { 266 regionPrice, ok := rate.Info[regionId] 267 if !ok { 268 return nil, fmt.Errorf("failed to found region %s price info", regionId) 269 } 270 computePrice, ok := regionPrice["Compute"] 271 if !ok { 272 return nil, fmt.Errorf("failed to found region %s compute price info", regionId) 273 } 274 return computePrice, nil 275 } 276 277 func (rate *SRateInfo) GetSharedSkuPrice(regionId string, sku string) (float64, error) { 278 computePrice, err := rate.GetComputePrice(regionId) 279 if err != nil { 280 return 0.0, errors.Wrap(err, "GetComputePrice") 281 } 282 if _sharedSku, ok := computePrice[sku]; ok { 283 if sharedSku, ok := _sharedSku[sku]; ok { 284 return sharedSku["0"], nil 285 } 286 } 287 return 0.0, fmt.Errorf("sku is not shared sku") 288 } 289 290 func (rate *SRateInfo) GetSkuPrice(regionId string, sku string, cpu, memoryMb int) (struct { 291 Hour float64 292 Month float64 293 Year float64 294 }, error) { 295 result := struct { 296 Hour float64 297 Month float64 298 Year float64 299 }{} 300 301 discount := rate.GetDiscount(sku) 302 303 price, err := rate.GetSharedSkuPrice(regionId, sku) 304 if err != nil { 305 skuType, err := rate.GetSkuType(sku) 306 if err != nil { 307 return result, errors.Wrap(err, "GetSkuType") 308 } 309 310 cpuPrice, err := rate.GetCpuPrice(regionId, skuType) 311 if err != nil { 312 return result, errors.Wrap(err, "price.GetCpuPrice") 313 } 314 315 price += float64(cpu) * cpuPrice 316 log.Debugf("cpu price: %f", cpuPrice) 317 318 extendMemoryGb := rate.GetExtendMemoryGb(sku, cpu, memoryMb) 319 memoryMb = int(float64(memoryMb) - 1024*extendMemoryGb) 320 321 memoryPrice, err := rate.GetMemoryPrice(regionId, skuType) 322 if err != nil { 323 return result, errors.Wrap(err, "GetMemoryPrice") 324 } 325 326 price += memoryPrice * float64(memoryMb/1024) 327 log.Debugf("ramPrice: %f", memoryPrice) 328 329 if extendMemoryGb > 0 { 330 memoryType := "custom-extended" 331 if strings.HasPrefix(sku, "n2") { 332 memoryType = "n2-custom-extended" 333 } 334 extendPrice, err := rate.GetMemoryPrice(regionId, memoryType) 335 if err != nil { 336 return result, errors.Wrap(err, "GetMemoryPrice.Extend") 337 } 338 price += extendPrice * float64(extendMemoryGb) 339 log.Debugf("extendPrice: %f", extendPrice) 340 } 341 } 342 343 log.Debugf("totalPrice: %f", price) 344 345 result.Month = 182.5 * price 346 result.Month += 182.5 * (1 - discount[1]) * price 347 result.Month += 182.5 * (1 - discount[2]) * price 348 result.Month += 172.49999999999997 * (1 - discount[3]) * price 349 result.Hour = result.Month / 30 / 24 350 result.Year = result.Month * 12 351 return result, nil 352 }