Parcourir la source

Azure PV Pricing Updates. Still need to review to ensure proper cost units.

Matt Bolt il y a 6 ans
Parent
commit
1a5cf21920

+ 4 - 2
pkg/cloud/awsprovider.go

@@ -399,14 +399,16 @@ type awsPVKey struct {
 	StorageClassParameters map[string]string
 	StorageClassName       string
 	Name                   string
+	DefaultRegion          string
 }
 
-func (aws *AWS) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string) PVKey {
+func (aws *AWS) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
 	return &awsPVKey{
 		Labels:                 pv.Labels,
 		StorageClassName:       pv.Spec.StorageClassName,
 		StorageClassParameters: parameters,
 		Name:                   pv.Name,
+		DefaultRegion:          defaultRegion,
 	}
 }
 
@@ -505,7 +507,7 @@ func (aws *AWS) DownloadPricingData() error {
 			klog.V(2).Infof("Unable to find params for storageClassName %s, falling back to default pricing", pv.Spec.StorageClassName)
 			continue
 		}
-		key := aws.GetPVKey(pv, params)
+		key := aws.GetPVKey(pv, params, "")
 		pvkeys[key.Features()] = key
 	}
 

+ 122 - 56
pkg/cloud/azureprovider.go

@@ -26,6 +26,11 @@ import (
 	"k8s.io/klog"
 )
 
+const (
+	AzurePremiumStorageClass  = "premium"
+	AzureStandardStorageClass = "standard"
+)
+
 var (
 	regionCodeMappings = map[string]string{
 		"ap": "asia",
@@ -161,8 +166,14 @@ func checkRegionID(regionID string, regions map[string]string) bool {
 	return false
 }
 
+// AzurePricing either contains a Node or PV
+type AzurePricing struct {
+	Node *Node
+	PV   *PV
+}
+
 type Azure struct {
-	allPrices               map[string]*Node
+	Pricing                 map[string]*AzurePricing
 	DownloadPricingDataLock sync.RWMutex
 	Clientset               clustercache.ClusterCache
 	Config                  *ProviderConfig
@@ -307,6 +318,7 @@ func (az *Azure) DownloadPricingData() error {
 	if err != nil {
 		return err
 	}
+
 	var authorizer autorest.Authorizer
 
 	if config.AzureClientID != "" && config.AzureClientSecret != "" && config.AzureTenantID != "" {
@@ -352,7 +364,7 @@ func (az *Azure) DownloadPricingData() error {
 	if err != nil {
 		return err
 	}
-	allPrices := make(map[string]*Node)
+	allPrices := make(map[string]*AzurePricing)
 	regions, err := getRegions("compute", sClient, providersClient, config.AzureSubscriptionID)
 	if err != nil {
 		return err
@@ -365,61 +377,101 @@ func (az *Azure) DownloadPricingData() error {
 	baseCPUPrice := c.CPU
 
 	for _, v := range *result.Meters {
-		if !strings.Contains(*v.MeterSubCategory, "Windows") && strings.Contains(*v.MeterCategory, "Virtual Machines") {
+		meterName := *v.MeterName
+		meterRegion := *v.MeterRegion
+		meterCategory := *v.MeterCategory
+		meterSubCategory := *v.MeterSubCategory
 
-			region, err := toRegionID(*v.MeterRegion, regions)
-			if err != nil {
-				continue
-			}
+		region, err := toRegionID(meterRegion, regions)
+		if err != nil {
+			continue
+		}
 
-			meterName := *v.MeterName
-			sc := *v.MeterSubCategory
+		if !strings.Contains(meterSubCategory, "Windows") {
 
-			// not available now
-			if strings.Contains(sc, "Promo") {
-				continue
-			}
+			if strings.Contains(meterCategory, "Storage") {
+				if strings.Contains(meterSubCategory, "HDD") || strings.Contains(meterSubCategory, "SSD") {
+					var storageClass string = ""
+					if strings.Contains(meterName, "S4 ") {
+						storageClass = AzureStandardStorageClass
+					} else if strings.Contains(meterName, "P4 ") {
+						storageClass = AzurePremiumStorageClass
+					}
 
-			usageType := ""
-			if !strings.Contains(meterName, "Low Priority") {
-				usageType = "ondemand"
-			} else {
-				usageType = "preemptible"
-			}
+					if storageClass != "" {
+						var priceInUsd float64
 
-			var instanceTypes []string
-			name := strings.TrimSuffix(meterName, " Low Priority")
-			instanceType := strings.Split(name, "/")
-			for _, it := range instanceType {
-				instanceTypes = append(instanceTypes, strings.Replace(it, " ", "_", 1))
+						if len(v.MeterRates) < 1 {
+							klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
+							continue
+						}
+						for _, rate := range v.MeterRates {
+							priceInUsd += *rate
+						}
+						priceStr := fmt.Sprintf("%f", priceInUsd)
+
+						key := region + "," + storageClass
+						klog.V(4).Infof("Adding PV.Key: %s, Cost: %s", key, priceStr)
+						allPrices[key] = &AzurePricing{
+							PV: &PV{
+								Cost:   priceStr,
+								Region: region,
+							},
+						}
+					}
+				}
 			}
 
-			instanceTypes = transformMachineType(sc, instanceTypes)
-			if strings.Contains(name, "Expired") {
-				instanceTypes = []string{}
-			}
+			if strings.Contains(meterCategory, "Virtual Machines") {
 
-			var priceInUsd float64
+				// not available now
+				if strings.Contains(meterSubCategory, "Promo") {
+					continue
+				}
 
-			if len(v.MeterRates) < 1 {
-				klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
-				continue
-			}
-			for _, rate := range v.MeterRates {
-				priceInUsd += *rate
-			}
-			priceStr := fmt.Sprintf("%f", priceInUsd)
-			for _, instanceType := range instanceTypes {
+				usageType := ""
+				if !strings.Contains(meterName, "Low Priority") {
+					usageType = "ondemand"
+				} else {
+					usageType = "preemptible"
+				}
+
+				var instanceTypes []string
+				name := strings.TrimSuffix(meterName, " Low Priority")
+				instanceType := strings.Split(name, "/")
+				for _, it := range instanceType {
+					instanceTypes = append(instanceTypes, strings.Replace(it, " ", "_", 1))
+				}
+
+				instanceTypes = transformMachineType(meterSubCategory, instanceTypes)
+				if strings.Contains(name, "Expired") {
+					instanceTypes = []string{}
+				}
 
-				key := fmt.Sprintf("%s,%s,%s", region, instanceType, usageType)
-				allPrices[key] = &Node{
-					Cost:         priceStr,
-					BaseCPUPrice: baseCPUPrice,
+				var priceInUsd float64
+
+				if len(v.MeterRates) < 1 {
+					klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
+					continue
+				}
+				for _, rate := range v.MeterRates {
+					priceInUsd += *rate
+				}
+				priceStr := fmt.Sprintf("%f", priceInUsd)
+				for _, instanceType := range instanceTypes {
+
+					key := fmt.Sprintf("%s,%s,%s", region, instanceType, usageType)
+					allPrices[key] = &AzurePricing{
+						Node: &Node{
+							Cost:         priceStr,
+							BaseCPUPrice: baseCPUPrice,
+						},
+					}
 				}
 			}
 		}
 	}
-	az.allPrices = allPrices
+	az.Pricing = allPrices
 	return nil
 }
 
@@ -427,19 +479,19 @@ func (az *Azure) DownloadPricingData() error {
 func (az *Azure) AllNodePricing() (interface{}, error) {
 	az.DownloadPricingDataLock.RLock()
 	defer az.DownloadPricingDataLock.RUnlock()
-	return az.allPrices, nil
+	return az.Pricing, nil
 }
 
 // NodePricing returns Azure pricing data for a single node
 func (az *Azure) NodePricing(key Key) (*Node, error) {
 	az.DownloadPricingDataLock.RLock()
 	defer az.DownloadPricingDataLock.RUnlock()
-	if n, ok := az.allPrices[key.Features()]; ok {
+	if n, ok := az.Pricing[key.Features()]; ok {
 		klog.V(4).Infof("Returning pricing for node %s: %+v from key %s", key, n, key.Features())
 		if key.GPUType() != "" {
-			n.GPU = "1" // TODO: support multiple GPUs
+			n.Node.GPU = "1" // TODO: support multiple GPUs
 		}
-		return n, nil
+		return n.Node, nil
 	}
 	klog.V(1).Infof("[Warning] no pricing data found for %s: %s", key.Features(), key)
 	c, err := az.GetConfig()
@@ -491,13 +543,15 @@ type azurePvKey struct {
 	Labels                 map[string]string
 	StorageClass           string
 	StorageClassParameters map[string]string
+	DefaultRegion          string
 }
 
-func (az *Azure) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string) PVKey {
+func (az *Azure) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
 	return &azurePvKey{
 		Labels:                 pv.Labels,
 		StorageClass:           pv.Spec.StorageClassName,
 		StorageClassParameters: parameters,
+		DefaultRegion:          defaultRegion,
 	}
 }
 
@@ -506,13 +560,17 @@ func (key *azurePvKey) GetStorageClass() string {
 }
 
 func (key *azurePvKey) Features() string {
-	storageClass := key.StorageClassParameters["type"]
-	if storageClass == "pd-ssd" {
-		storageClass = "ssd"
-	} else if storageClass == "pd-standard" {
-		storageClass = "pdstandard"
+	storageClass := key.StorageClassParameters["storageaccounttype"]
+	if strings.EqualFold(storageClass, "Premium_LRS") {
+		storageClass = AzurePremiumStorageClass
+	} else if strings.EqualFold(storageClass, "Standard_LRS") {
+		storageClass = AzureStandardStorageClass
+	}
+	if region, ok := key.Labels[v1.LabelZoneRegion]; ok {
+		return region + "," + storageClass
 	}
-	return key.Labels[v1.LabelZoneRegion] + "," + storageClass
+
+	return key.DefaultRegion + "," + storageClass
 }
 
 func (*Azure) GetDisks() ([]byte, error) {
@@ -616,8 +674,16 @@ func (az *Azure) ApplyReservedInstancePricing(nodes map[string]*Node) {
 
 }
 
-func (az *Azure) PVPricing(PVKey) (*PV, error) {
-	return nil, nil
+func (az *Azure) PVPricing(pvk PVKey) (*PV, error) {
+	az.DownloadPricingDataLock.RLock()
+	defer az.DownloadPricingDataLock.RUnlock()
+
+	pricing, ok := az.Pricing[pvk.Features()]
+	if !ok {
+		klog.V(4).Infof("Persistent Volume pricing not found for %s: %s", pvk.GetStorageClass(), pvk.Features())
+		return &PV{}, nil
+	}
+	return pricing.PV, nil
 }
 
 func (az *Azure) GetLocalStorageQuery(window, offset string, rate bool) string {

+ 5 - 3
pkg/cloud/customprovider.go

@@ -234,10 +234,12 @@ func (cp *CustomProvider) NetworkPricing() (*Network, error) {
 	}, nil
 }
 
-func (*CustomProvider) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string) PVKey {
+func (*CustomProvider) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
 	return &awsPVKey{
-		Labels:           pv.Labels,
-		StorageClassName: pv.Spec.StorageClassName,
+		Labels:                 pv.Labels,
+		StorageClassName:       pv.Spec.StorageClassName,
+		StorageClassParameters: parameters,
+		DefaultRegion:          defaultRegion,
 	}
 }
 

+ 4 - 2
pkg/cloud/gcpprovider.go

@@ -879,7 +879,7 @@ func (gcp *GCP) DownloadPricingData() error {
 			klog.Infof("Unable to find params for storageClassName %s", pv.Name)
 			continue
 		}
-		key := gcp.GetPVKey(pv, params)
+		key := gcp.GetPVKey(pv, params, "")
 		pvkeys[key.Features()] = key
 	}
 
@@ -1167,17 +1167,19 @@ type pvKey struct {
 	Labels                 map[string]string
 	StorageClass           string
 	StorageClassParameters map[string]string
+	DefaultRegion          string
 }
 
 func (key *pvKey) GetStorageClass() string {
 	return key.StorageClass
 }
 
-func (gcp *GCP) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string) PVKey {
+func (gcp *GCP) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
 	return &pvKey{
 		Labels:                 pv.Labels,
 		StorageClass:           pv.Spec.StorageClassName,
 		StorageClassParameters: parameters,
+		DefaultRegion:          defaultRegion,
 	}
 }
 

+ 1 - 1
pkg/cloud/provider.go

@@ -165,7 +165,7 @@ type Provider interface {
 	AllNodePricing() (interface{}, error)
 	DownloadPricingData() error
 	GetKey(map[string]string) Key
-	GetPVKey(*v1.PersistentVolume, map[string]string) PVKey
+	GetPVKey(*v1.PersistentVolume, map[string]string, string) PVKey
 	UpdateConfig(r io.Reader, updateType string) (*CustomPricing, error)
 	UpdateConfigFromConfigMap(map[string]string) (*CustomPricing, error)
 	GetConfig() (*CustomPricing, error)

+ 18 - 4
pkg/costmodel/costmodel.go

@@ -969,6 +969,13 @@ func addPVData(cache clustercache.ClusterCache, pvClaimMapping map[string]*Persi
 	if err != nil {
 		return err
 	}
+	// Pull a region from the first node
+	var defaultRegion string
+	nodeList := cache.GetAllNodes()
+	if len(nodeList) > 0 {
+		defaultRegion = nodeList[0].Labels[v1.LabelZoneRegion]
+	}
+
 	storageClasses := cache.GetAllStorageClasses()
 	storageClassMap := make(map[string]map[string]string)
 	for _, storageClass := range storageClasses {
@@ -987,12 +994,19 @@ func addPVData(cache clustercache.ClusterCache, pvClaimMapping map[string]*Persi
 		if !ok {
 			klog.V(4).Infof("Unable to find parameters for storage class \"%s\". Does pv \"%s\" have a storageClassName?", pv.Spec.StorageClassName, pv.Name)
 		}
+		var region string
+		if r, ok := pv.Labels[v1.LabelZoneRegion]; ok {
+			region = r
+		} else {
+			region = defaultRegion
+		}
+
 		cacPv := &costAnalyzerCloud.PV{
 			Class:      pv.Spec.StorageClassName,
-			Region:     pv.Labels[v1.LabelZoneRegion],
+			Region:     region,
 			Parameters: parameters,
 		}
-		err := GetPVCost(cacPv, pv, cloud)
+		err := GetPVCost(cacPv, pv, cloud, region)
 		if err != nil {
 			return err
 		}
@@ -1012,12 +1026,12 @@ func addPVData(cache clustercache.ClusterCache, pvClaimMapping map[string]*Persi
 	return nil
 }
 
-func GetPVCost(pv *costAnalyzerCloud.PV, kpv *v1.PersistentVolume, cp costAnalyzerCloud.Provider) error {
+func GetPVCost(pv *costAnalyzerCloud.PV, kpv *v1.PersistentVolume, cp costAnalyzerCloud.Provider, defaultRegion string) error {
 	cfg, err := cp.GetConfig()
 	if err != nil {
 		return err
 	}
-	key := cp.GetPVKey(kpv, pv.Parameters)
+	key := cp.GetPVKey(kpv, pv.Parameters, defaultRegion)
 	pvWithCost, err := cp.PVPricing(key)
 	if err != nil {
 		pv.Cost = cfg.Storage

+ 14 - 2
pkg/costmodel/router.go

@@ -639,6 +639,12 @@ func (a *Accesses) recordPrices() {
 			return strings.Split(key, ",")
 		}
 
+		var defaultRegion string = ""
+		nodeList := a.Model.Cache.GetAllNodes()
+		if len(nodeList) > 0 {
+			defaultRegion = nodeList[0].Labels[v1.LabelZoneRegion]
+		}
+
 		for {
 			klog.V(4).Info("Recording prices...")
 			podlist := a.Model.Cache.GetAllPods()
@@ -765,12 +771,18 @@ func (a *Accesses) recordPrices() {
 					if !ok {
 						klog.V(4).Infof("Unable to find parameters for storage class \"%s\". Does pv \"%s\" have a storageClassName?", pv.Spec.StorageClassName, pv.Name)
 					}
+					var region string
+					if r, ok := pv.Labels[v1.LabelZoneRegion]; ok {
+						region = r
+					} else {
+						region = defaultRegion
+					}
 					cacPv := &costAnalyzerCloud.PV{
 						Class:      pv.Spec.StorageClassName,
-						Region:     pv.Labels[v1.LabelZoneRegion],
+						Region:     region,
 						Parameters: parameters,
 					}
-					GetPVCost(cacPv, pv, a.Cloud)
+					GetPVCost(cacPv, pv, a.Cloud, region)
 					c, _ := strconv.ParseFloat(cacPv.Cost, 64)
 					a.PersistentVolumePriceRecorder.WithLabelValues(pv.Name, pv.Name).Set(c)
 					labelKey := getKeyFromLabelStrings(pv.Name, pv.Name)