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  }