AjayTripathy 7 лет назад
Родитель
Сommit
ebd45f0ace
5 измененных файлов с 289 добавлено и 106 удалено
  1. 59 0
      cloud/awsprovider.go
  2. 123 33
      cloud/gcpprovider.go
  3. 19 0
      cloud/provider.go
  4. 63 73
      costmodel/costmodel.go
  5. 25 0
      main.go

+ 59 - 0
cloud/awsprovider.go

@@ -76,6 +76,8 @@ type AWSProductAttributes struct {
 	UsageType       string `json:"usagetype"`
 	UsageType       string `json:"usagetype"`
 	OperatingSystem string `json:"operatingSystem"`
 	OperatingSystem string `json:"operatingSystem"`
 	PreInstalledSw  string `json:"preInstalledSw"`
 	PreInstalledSw  string `json:"preInstalledSw"`
+	InstanceFamily  string `json:"instanceFamily"`
+	GPU             string `json:gpu`
 }
 }
 
 
 // AWSPricingTerms are how you pay for the node: OnDemand, Reserved, or (TODO) Spot
 // AWSPricingTerms are how you pay for the node: OnDemand, Reserved, or (TODO) Spot
@@ -109,6 +111,7 @@ type AWSProductTerms struct {
 	Memory   string        `json:"memory"`
 	Memory   string        `json:"memory"`
 	Storage  string        `json:"storage"`
 	Storage  string        `json:"storage"`
 	VCpu     string        `json:"vcpu"`
 	VCpu     string        `json:"vcpu"`
+	GPU      string        `json:"gpu"`
 }
 }
 
 
 // ClusterIdEnvVar is the environment variable in which one can manually set the ClusterId
 // ClusterIdEnvVar is the environment variable in which one can manually set the ClusterId
@@ -155,6 +158,54 @@ func (aws *AWS) KubeAttrConversion(location, instanceType, operatingSystem strin
 	return region + "," + instanceType + "," + operatingSystem
 	return region + "," + instanceType + "," + operatingSystem
 }
 }
 
 
+type AwsSpotFeedInfo struct {
+	BucketName       string `json:"bucketName"`
+	Prefix           string `json:"prefix"`
+	Region           string `json:"region"`
+	AccountID        string `json:"accountId"`
+	ServiceKeyName   string `json:"serviceKeyName"`
+	ServiceKeySecret string `json:"serviceKeySecret"`
+}
+
+func (aws *AWS) GetConfig() (*CustomPricing, error) {
+	c, err := GetDefaultPricingData("aws.json")
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+}
+
+func (aws *AWS) UpdateConfig(r io.Reader) (*CustomPricing, error) {
+	a := AwsSpotFeedInfo{}
+	err := json.NewDecoder(r).Decode(&a)
+	if err != nil {
+		return nil, err
+	}
+
+	c, err := GetDefaultPricingData("aws.json")
+	if err != nil {
+		return nil, err
+	}
+	c.ServiceKeyName = a.ServiceKeyName
+	c.ServiceKeySecret = a.ServiceKeySecret
+	c.SpotDataPrefix = a.Prefix
+	c.SpotDataBucket = a.BucketName
+	c.ProjectID = a.AccountID
+	c.SpotDataRegion = a.Region
+
+	cj, err := json.Marshal(c)
+	if err != nil {
+		return nil, err
+	}
+
+	err = ioutil.WriteFile("/models/aws.json", cj, 0644)
+	if err != nil {
+		return nil, err
+	}
+	return c, nil
+
+}
+
 type awsKey struct {
 type awsKey struct {
 	SpotLabelName  string
 	SpotLabelName  string
 	SpotLabelValue string
 	SpotLabelValue string
@@ -162,6 +213,10 @@ type awsKey struct {
 	ProviderID     string
 	ProviderID     string
 }
 }
 
 
+func (k *awsKey) GPUType() string {
+	return ""
+}
+
 func (k *awsKey) ID() string {
 func (k *awsKey) ID() string {
 	provIdRx := regexp.MustCompile("aws:///([^/]+)/([^/]+)") // It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
 	provIdRx := regexp.MustCompile("aws:///([^/]+)/([^/]+)") // It's of the form aws:///us-east-2a/i-0fea4fd46592d050b and we want i-0fea4fd46592d050b, if it exists
 	for matchNum, group := range provIdRx.FindStringSubmatch(k.ProviderID) {
 	for matchNum, group := range provIdRx.FindStringSubmatch(k.ProviderID) {
@@ -291,6 +346,7 @@ func (aws *AWS) DownloadPricingData() error {
 							Memory:  product.Attributes.Memory,
 							Memory:  product.Attributes.Memory,
 							Storage: product.Attributes.Storage,
 							Storage: product.Attributes.Storage,
 							VCpu:    product.Attributes.VCpu,
 							VCpu:    product.Attributes.VCpu,
+							GPU:     product.Attributes.GPU,
 						}
 						}
 						aws.Pricing[key] = productTerms
 						aws.Pricing[key] = productTerms
 						aws.Pricing[spotKey] = productTerms
 						aws.Pricing[spotKey] = productTerms
@@ -384,6 +440,7 @@ func (aws *AWS) createNode(terms *AWSProductTerms, usageType string, k Key) (*No
 				Cost:         spotcost,
 				Cost:         spotcost,
 				VCPU:         terms.VCpu,
 				VCPU:         terms.VCpu,
 				RAM:          terms.Memory,
 				RAM:          terms.Memory,
+				GPU:          terms.GPU,
 				Storage:      terms.Storage,
 				Storage:      terms.Storage,
 				BaseCPUPrice: aws.BaseCPUPrice,
 				BaseCPUPrice: aws.BaseCPUPrice,
 				UsageType:    usageType,
 				UsageType:    usageType,
@@ -393,6 +450,7 @@ func (aws *AWS) createNode(terms *AWSProductTerms, usageType string, k Key) (*No
 			VCPU:         terms.VCpu,
 			VCPU:         terms.VCpu,
 			VCPUCost:     aws.BaseSpotCPUPrice,
 			VCPUCost:     aws.BaseSpotCPUPrice,
 			RAM:          terms.Memory,
 			RAM:          terms.Memory,
+			GPU:          terms.GPU,
 			RAMCost:      aws.BaseSpotRAMPrice,
 			RAMCost:      aws.BaseSpotRAMPrice,
 			Storage:      terms.Storage,
 			Storage:      terms.Storage,
 			BaseCPUPrice: aws.BaseCPUPrice,
 			BaseCPUPrice: aws.BaseCPUPrice,
@@ -404,6 +462,7 @@ func (aws *AWS) createNode(terms *AWSProductTerms, usageType string, k Key) (*No
 		Cost:         cost,
 		Cost:         cost,
 		VCPU:         terms.VCpu,
 		VCPU:         terms.VCpu,
 		RAM:          terms.Memory,
 		RAM:          terms.Memory,
+		GPU:          terms.GPU,
 		Storage:      terms.Storage,
 		Storage:      terms.Storage,
 		BaseCPUPrice: aws.BaseCPUPrice,
 		BaseCPUPrice: aws.BaseCPUPrice,
 		UsageType:    usageType,
 		UsageType:    usageType,

+ 123 - 33
cloud/gcpprovider.go

@@ -9,6 +9,7 @@ import (
 	"math"
 	"math"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"regexp"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 
 
@@ -71,6 +72,14 @@ func gcpAllocationToOutOfClusterAllocation(gcpAlloc gcpAllocation) *OutOfCluster
 	}
 	}
 }
 }
 
 
+func (gcp *GCP) GetConfig() (*CustomPricing, error) {
+	return nil, nil
+}
+
+func (gcp *GCP) UpdateConfig(r io.Reader) (*CustomPricing, error) {
+	return nil, nil
+}
+
 func (gcp *GCP) ExternalAllocations(start string, end string) ([]*OutOfClusterAllocation, error) {
 func (gcp *GCP) ExternalAllocations(start string, end string) ([]*OutOfClusterAllocation, error) {
 	// start, end formatted like: "2019-04-20 00:00:00"
 	// start, end formatted like: "2019-04-20 00:00:00"
 	queryString := fmt.Sprintf(`SELECT
 	queryString := fmt.Sprintf(`SELECT
@@ -225,7 +234,7 @@ type GCPResourceInfo struct {
 	UsageType          string `json:"usageType"`
 	UsageType          string `json:"usageType"`
 }
 }
 
 
-func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]bool) (map[string]*GCPPricing, string, error) {
+func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]Key) (map[string]*GCPPricing, string, error) {
 	gcpPricingList := make(map[string]*GCPPricing)
 	gcpPricingList := make(map[string]*GCPPricing)
 	var nextPageToken string
 	var nextPageToken string
 	dec := json.NewDecoder(r)
 	dec := json.NewDecoder(r)
@@ -252,6 +261,7 @@ func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]bool) (map[string]*G
 				if (instanceType == "ram" || instanceType == "cpu") && strings.Contains(strings.ToUpper(product.Description), "CUSTOM") {
 				if (instanceType == "ram" || instanceType == "cpu") && strings.Contains(strings.ToUpper(product.Description), "CUSTOM") {
 					instanceType = "custom"
 					instanceType = "custom"
 				}
 				}
+
 				var partialCPU float64
 				var partialCPU float64
 				if strings.ToLower(instanceType) == "f1micro" {
 				if strings.ToLower(instanceType) == "f1micro" {
 					partialCPU = 0.2
 					partialCPU = 0.2
@@ -259,11 +269,21 @@ func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]bool) (map[string]*G
 					partialCPU = 0.5
 					partialCPU = 0.5
 				}
 				}
 
 
+				var gpuType string
+				provIdRx := regexp.MustCompile("(Nvidia Tesla [^ ]+) ")
+				for matchnum, group := range provIdRx.FindStringSubmatch(product.Description) {
+					if matchnum == 1 {
+						gpuType = strings.ToLower(strings.Join(strings.Split(group, " "), "-"))
+						klog.V(3).Info("GPU TYPE FOUND: " + gpuType)
+					}
+				}
+
 				for _, sr := range product.ServiceRegions {
 				for _, sr := range product.ServiceRegions {
 					region := sr
 					region := sr
-
 					candidateKey := region + "," + instanceType + "," + usageType
 					candidateKey := region + "," + instanceType + "," + usageType
-					if _, ok := inputKeys[candidateKey]; ok {
+					candidateKeyGPU := candidateKey + ",gpu"
+
+					if gpuType != "" {
 						lastRateIndex := len(product.PricingInfo[0].PricingExpression.TieredRates) - 1
 						lastRateIndex := len(product.PricingInfo[0].PricingExpression.TieredRates) - 1
 						var nanos float64
 						var nanos float64
 						if len(product.PricingInfo) > 0 {
 						if len(product.PricingInfo) > 0 {
@@ -271,41 +291,98 @@ func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]bool) (map[string]*G
 						} else {
 						} else {
 							continue
 							continue
 						}
 						}
-
 						hourlyPrice := nanos * math.Pow10(-9)
 						hourlyPrice := nanos * math.Pow10(-9)
-						if hourlyPrice == 0 {
-							continue
-						} else if strings.Contains(strings.ToUpper(product.Description), "RAM") {
-							if instanceType == "custom" {
-								klog.V(2).Infof("RAM custom sku is: " + product.Name)
+
+						for k, key := range inputKeys {
+							if key.GPUType() == gpuType {
+								if region == strings.Split(k, ",")[0] {
+									klog.V(3).Infof("MATCHED GPU TO NODE in region " + region)
+									candidateKeyGPU = key.Features()
+									if pl, ok := gcpPricingList[candidateKeyGPU]; ok {
+										pl.Node.GPUName = gpuType
+										pl.Node.GPUCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+										pl.Node.GPU = "1"
+									} else {
+										product.Node = &Node{
+											GPUName: gpuType,
+											GPUCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+											GPU:     "1",
+										}
+										klog.V(3).Infof("Added data for " + candidateKeyGPU)
+										gcpPricingList[candidateKeyGPU] = product
+									}
+								}
 							}
 							}
-							if _, ok := gcpPricingList[candidateKey]; ok {
-								gcpPricingList[candidateKey].Node.RAMCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+						}
+					} else {
+						if _, ok := inputKeys[candidateKey]; ok {
+							lastRateIndex := len(product.PricingInfo[0].PricingExpression.TieredRates) - 1
+							var nanos float64
+							if len(product.PricingInfo) > 0 {
+								nanos = product.PricingInfo[0].PricingExpression.TieredRates[lastRateIndex].UnitPrice.Nanos
 							} else {
 							} else {
-								product.Node = &Node{
-									RAMCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+								continue
+							}
+							hourlyPrice := nanos * math.Pow10(-9)
+
+							if hourlyPrice == 0 {
+								continue
+							} else if strings.Contains(strings.ToUpper(product.Description), "RAM") {
+								if instanceType == "custom" {
+									klog.V(2).Infof("RAM custom sku is: " + product.Name)
 								}
 								}
-								if partialCPU != 0 {
-									product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+								if _, ok := gcpPricingList[candidateKey]; ok {
+									gcpPricingList[candidateKey].Node.RAMCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+								} else {
+									product.Node = &Node{
+										RAMCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+									}
+									if partialCPU != 0 {
+										product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+									}
+									product.Node.UsageType = usageType
+									gcpPricingList[candidateKey] = product
 								}
 								}
-								product.Node.UsageType = usageType
-								gcpPricingList[candidateKey] = product
-							}
-							break
-						} else {
-							if _, ok := gcpPricingList[candidateKey]; ok {
-								gcpPricingList[candidateKey].Node.VCPUCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+								if _, ok := gcpPricingList[candidateKeyGPU]; ok {
+									gcpPricingList[candidateKeyGPU].Node.RAMCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+								} else {
+									product.Node = &Node{
+										RAMCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+									}
+									if partialCPU != 0 {
+										product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+									}
+									product.Node.UsageType = usageType
+									gcpPricingList[candidateKeyGPU] = product
+								}
+								break
 							} else {
 							} else {
-								product.Node = &Node{
-									VCPUCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+								if _, ok := gcpPricingList[candidateKey]; ok {
+									gcpPricingList[candidateKey].Node.VCPUCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+								} else {
+									product.Node = &Node{
+										VCPUCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+									}
+									if partialCPU != 0 {
+										product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+									}
+									product.Node.UsageType = usageType
+									gcpPricingList[candidateKey] = product
 								}
 								}
-								if partialCPU != 0 {
-									product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+								if _, ok := gcpPricingList[candidateKeyGPU]; ok {
+									gcpPricingList[candidateKey].Node.VCPUCost = strconv.FormatFloat(hourlyPrice, 'f', -1, 64)
+								} else {
+									product.Node = &Node{
+										VCPUCost: strconv.FormatFloat(hourlyPrice, 'f', -1, 64),
+									}
+									if partialCPU != 0 {
+										product.Node.VCPU = fmt.Sprintf("%f", partialCPU)
+									}
+									product.Node.UsageType = usageType
+									gcpPricingList[candidateKeyGPU] = product
 								}
 								}
-								product.Node.UsageType = usageType
-								gcpPricingList[candidateKey] = product
+								break
 							}
 							}
-							break
 						}
 						}
 					}
 					}
 				}
 				}
@@ -327,7 +404,7 @@ func (gcp *GCP) parsePage(r io.Reader, inputKeys map[string]bool) (map[string]*G
 	return gcpPricingList, nextPageToken, nil
 	return gcpPricingList, nextPageToken, nil
 }
 }
 
 
-func (gcp *GCP) parsePages(inputKeys map[string]bool) (map[string]*GCPPricing, error) {
+func (gcp *GCP) parsePages(inputKeys map[string]Key) (map[string]*GCPPricing, error) {
 	var pages []map[string]*GCPPricing
 	var pages []map[string]*GCPPricing
 	url := "https://cloudbilling.googleapis.com/v1/services/6F81-5844-456A/skus?key=" + gcp.APIKey
 	url := "https://cloudbilling.googleapis.com/v1/services/6F81-5844-456A/skus?key=" + gcp.APIKey
 	klog.V(2).Infof("Fetch GCP Billing Data from URL: %s", url)
 	klog.V(2).Infof("Fetch GCP Billing Data from URL: %s", url)
@@ -387,12 +464,12 @@ func (gcp *GCP) DownloadPricingData() error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	inputkeys := make(map[string]bool)
+	inputkeys := make(map[string]Key)
 
 
 	for _, n := range nodeList.Items {
 	for _, n := range nodeList.Items {
 		labels := n.GetObjectMeta().GetLabels()
 		labels := n.GetObjectMeta().GetLabels()
 		key := gcp.GetKey(labels)
 		key := gcp.GetKey(labels)
-		inputkeys[key.Features()] = true
+		inputkeys[key.Features()] = key
 	}
 	}
 
 
 	pages, err := gcp.parsePages(inputkeys)
 	pages, err := gcp.parsePages(inputkeys)
@@ -419,6 +496,14 @@ func (gcp *gcpKey) ID() string {
 	return ""
 	return ""
 }
 }
 
 
+func (gcp *gcpKey) GPUType() string {
+	if t, ok := gcp.Labels["cloud.google.com/gke-accelerator"]; ok {
+		klog.V(3).Infof("GPU of type: \"%s\" found", t)
+		return t
+	}
+	return ""
+}
+
 // GetKey maps node labels to information needed to retrieve pricing data
 // GetKey maps node labels to information needed to retrieve pricing data
 func (gcp *gcpKey) Features() string {
 func (gcp *gcpKey) Features() string {
 	instanceType := strings.ToLower(strings.Join(strings.Split(gcp.Labels[v1.LabelInstanceType], "-")[:2], ""))
 	instanceType := strings.ToLower(strings.Join(strings.Split(gcp.Labels[v1.LabelInstanceType], "-")[:2], ""))
@@ -435,6 +520,11 @@ func (gcp *gcpKey) Features() string {
 	} else {
 	} else {
 		usageType = "ondemand"
 		usageType = "ondemand"
 	}
 	}
+
+	if _, ok := gcp.Labels["cloud.google.com/gke-accelerator"]; ok {
+		return region + "," + instanceType + "," + usageType + "," + "gpu"
+	}
+
 	return region + "," + instanceType + "," + usageType
 	return region + "," + instanceType + "," + usageType
 }
 }
 
 
@@ -450,6 +540,6 @@ func (gcp *GCP) NodePricing(key Key) (*Node, error) {
 		n.Node.BaseCPUPrice = gcp.BaseCPUPrice
 		n.Node.BaseCPUPrice = gcp.BaseCPUPrice
 		return n.Node, nil
 		return n.Node, nil
 	}
 	}
-	klog.V(1).Infof("Warning: no pricing data found for %s", key)
+	klog.V(1).Infof("Warning: no pricing data found for %s: %s", key.Features(), key)
 	return nil, fmt.Errorf("Warning: no pricing data found for %s", key)
 	return nil, fmt.Errorf("Warning: no pricing data found for %s", key)
 }
 }

+ 19 - 0
cloud/provider.go

@@ -3,6 +3,7 @@ package cloud
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
+	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
@@ -30,12 +31,16 @@ type Node struct {
 	UsesBaseCPUPrice bool   `json:"usesDefaultPrice"`
 	UsesBaseCPUPrice bool   `json:"usesDefaultPrice"`
 	BaseCPUPrice     string `json:"baseCPUPrice"` // Used to compute an implicit RAM GB/Hr price when RAM pricing is not provided.
 	BaseCPUPrice     string `json:"baseCPUPrice"` // Used to compute an implicit RAM GB/Hr price when RAM pricing is not provided.
 	UsageType        string `json:"usageType"`
 	UsageType        string `json:"usageType"`
+	GPU              string `json:"gpu"`
+	GPUName          string `json:"gpuName"`
+	GPUCost          string `json:"gpuCost"`
 }
 }
 
 
 // Key represents a way for nodes to match between the k8s API and a pricing API
 // Key represents a way for nodes to match between the k8s API and a pricing API
 type Key interface {
 type Key interface {
 	ID() string       // ID represents an exact match
 	ID() string       // ID represents an exact match
 	Features() string // Features are a comma separated string of node metadata that could match pricing
 	Features() string // Features are a comma separated string of node metadata that could match pricing
+	GPUType() string  // GPUType returns "" if no GPU exists, but the name of the GPU otherwise
 }
 }
 
 
 // OutOfClusterAllocation represents a cloud provider cost not associated with kubernetes
 // OutOfClusterAllocation represents a cloud provider cost not associated with kubernetes
@@ -56,6 +61,8 @@ type Provider interface {
 	AllNodePricing() (interface{}, error)
 	AllNodePricing() (interface{}, error)
 	DownloadPricingData() error
 	DownloadPricingData() error
 	GetKey(map[string]string) Key
 	GetKey(map[string]string) Key
+	UpdateConfig(r io.Reader) (*CustomPricing, error)
+	GetConfig() (*CustomPricing, error)
 
 
 	ExternalAllocations(string, string) ([]*OutOfClusterAllocation, error)
 	ExternalAllocations(string, string) ([]*OutOfClusterAllocation, error)
 }
 }
@@ -109,6 +116,14 @@ type CustomProvider struct {
 	SpotLabelValue string
 	SpotLabelValue string
 }
 }
 
 
+func (*CustomProvider) GetConfig() (*CustomPricing, error) {
+	return nil, nil
+}
+
+func (*CustomProvider) UpdateConfig(r io.Reader) (*CustomPricing, error) {
+	return nil, nil
+}
+
 func (*CustomProvider) ClusterName() ([]byte, error) {
 func (*CustomProvider) ClusterName() ([]byte, error) {
 	return nil, nil
 	return nil, nil
 }
 }
@@ -163,6 +178,10 @@ type customProviderKey struct {
 	Labels         map[string]string
 	Labels         map[string]string
 }
 }
 
 
+func (c *customProviderKey) GPUType() string {
+	return ""
+}
+
 func (c *customProviderKey) ID() string {
 func (c *customProviderKey) ID() string {
 	return ""
 	return ""
 }
 }

+ 63 - 73
costmodel/costmodel.go

@@ -425,28 +425,60 @@ func getNodeCost(clientset kubernetes.Interface, cloud costAnalyzerCloud.Provide
 			cnode.RAM = n.Status.Capacity.Memory().String()
 			cnode.RAM = n.Status.Capacity.Memory().String()
 		}
 		}
 		ram = float64(n.Status.Capacity.Memory().Value())
 		ram = float64(n.Status.Capacity.Memory().Value())
-		if cnode.RAMCost == "" { // We couldn't find a ramcost, so fix cpu and allocate ram accordingly
-			basePrice, _ := strconv.ParseFloat(cnode.BaseCPUPrice, 64)
-			totalCPUPrice := basePrice * cpu
-			var nodePrice float64
-			if cnode.Cost != "" {
-				klog.V(3).Infof("Use given nodeprice as whole node price")
-				nodePrice, _ = strconv.ParseFloat(cnode.Cost, 64)
-			} else {
-				klog.V(3).Infof("Use cpuprice as whole node price")
-				nodePrice, _ = strconv.ParseFloat(cnode.VCPUCost, 64) // all the price was allocated the the CPU
+
+		if cnode.GPU != "" && cnode.GPUCost == "" { // We couldn't find a gpu cost, so fix cpu and ram, then accordingly
+			klog.V(3).Infof("GPU without cost found, calculating...")
+			basePrice, err := strconv.ParseFloat(cnode.BaseCPUPrice, 64)
+			if err != nil {
+				return nil, err
 			}
 			}
-			if totalCPUPrice >= nodePrice {
-				totalCPUPrice = 0.9 * nodePrice // just allocate RAM costs to 10% of the node price here to avoid 0 or negative in the numerator
+			nodePrice, err := strconv.ParseFloat(cnode.Cost, 64)
+			if err != nil {
+				return nil, err
 			}
 			}
-			ramPrice := (nodePrice - totalCPUPrice) / (ram / 1024 / 1024 / 1024)
-			cpuPrice := totalCPUPrice / cpu
-
-			cnode.VCPUCost = fmt.Sprintf("%f", cpuPrice)
+			totalCPUPrice := basePrice * cpu
+			totalRAMPrice := 0.1 * totalCPUPrice
+			ramPrice := totalRAMPrice / (ram / 1024 / 1024 / 1024)
+			gpuPrice := nodePrice - totalCPUPrice - totalRAMPrice
+			cnode.VCPUCost = fmt.Sprintf("%f", basePrice)
 			cnode.RAMCost = fmt.Sprintf("%f", ramPrice)
 			cnode.RAMCost = fmt.Sprintf("%f", ramPrice)
 			cnode.RAMBytes = fmt.Sprintf("%f", ram)
 			cnode.RAMBytes = fmt.Sprintf("%f", ram)
-			klog.V(2).Infof("Node \"%s\" RAM Cost := %v", name, cnode.RAMCost)
+			cnode.GPUCost = fmt.Sprintf("%f", gpuPrice)
+			klog.V(2).Infof("Computed \"%s\" GPU Cost := %v", name, cnode.GPUCost)
+		} else {
+			if cnode.RAMCost == "" { // We couldn't find a ramcost, so fix cpu and allocate ram accordingly
+				basePrice, err := strconv.ParseFloat(cnode.BaseCPUPrice, 64)
+				if err != nil {
+					return nil, err
+				}
+				totalCPUPrice := basePrice * cpu
+				var nodePrice float64
+				if cnode.Cost != "" {
+					klog.V(3).Infof("Use given nodeprice as whole node price")
+					nodePrice, err = strconv.ParseFloat(cnode.Cost, 64)
+					if err != nil {
+						return nil, err
+					}
+				} else {
+					klog.V(3).Infof("Use cpuprice as whole node price")
+					nodePrice, err = strconv.ParseFloat(cnode.VCPUCost, 64) // all the price was allocated the the CPU
+					if err != nil {
+						return nil, err
+					}
+				}
+				if totalCPUPrice >= nodePrice {
+					totalCPUPrice = 0.9 * nodePrice // just allocate RAM costs to 10% of the node price here to avoid 0 or negative in the numerator
+				}
+				ramPrice := (nodePrice - totalCPUPrice) / (ram / 1024 / 1024 / 1024)
+				cpuPrice := totalCPUPrice / cpu
+
+				cnode.VCPUCost = fmt.Sprintf("%f", cpuPrice)
+				cnode.RAMCost = fmt.Sprintf("%f", ramPrice)
+				cnode.RAMBytes = fmt.Sprintf("%f", ram)
+				klog.V(3).Infof("Computed \"%s\" RAM Cost := %v", name, cnode.RAMCost)
+			}
 		}
 		}
+
 		nodes[name] = cnode
 		nodes[name] = cnode
 	}
 	}
 	return nodes, nil
 	return nodes, nil
@@ -864,30 +896,9 @@ func getPVInfoVectors(qr interface{}) (map[string]*PersistentVolumeData, error)
 		if !ok {
 		if !ok {
 			return nil, fmt.Errorf("Metric field is improperly formatted")
 			return nil, fmt.Errorf("Metric field is improperly formatted")
 		}
 		}
-		pvclaim, ok := metricMap["persistentvolumeclaim"]
-		if !ok {
-			return nil, fmt.Errorf("Claim field does not exist in data result vector")
-		}
-		pvclaimStr, ok := pvclaim.(string)
-		if !ok {
-			return nil, fmt.Errorf("Claim field improperly formatted")
-		}
-		pvclass, ok := metricMap["storageclass"]
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field does not exist in data result vector")
-		}
-		pvclassStr, ok := pvclass.(string)
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field improperly formatted")
-		}
-		pvnamespace, ok := metricMap["namespace"]
-		if !ok {
-			return nil, fmt.Errorf("Namespace field does not exist in data result vector")
-		}
-		pvnamespaceStr, ok := pvnamespace.(string)
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field improperly formatted")
-		}
+		pvclaim := metricMap["persistentvolumeclaim"]
+		pvclass := metricMap["storageclass"]
+		pvnamespace := metricMap["namespace"]
 		values, ok := val.(map[string]interface{})["values"].([]interface{})
 		values, ok := val.(map[string]interface{})["values"].([]interface{})
 		if !ok {
 		if !ok {
 			return nil, fmt.Errorf("Values field is improperly formatted")
 			return nil, fmt.Errorf("Values field is improperly formatted")
@@ -906,11 +917,11 @@ func getPVInfoVectors(qr interface{}) (map[string]*PersistentVolumeData, error)
 				Value:     v,
 				Value:     v,
 			})
 			})
 		}
 		}
-		key := pvnamespaceStr + "," + pvclaimStr
+		key := pvnamespace.(string) + "," + pvclaim.(string)
 		pvmap[key] = &PersistentVolumeData{
 		pvmap[key] = &PersistentVolumeData{
-			Class:     pvclassStr,
-			Claim:     pvclaimStr,
-			Namespace: pvnamespaceStr,
+			Class:     pvclass.(string),
+			Claim:     pvclaim.(string),
+			Namespace: pvnamespace.(string),
 			Values:    vectors,
 			Values:    vectors,
 		}
 		}
 	}
 	}
@@ -928,30 +939,9 @@ func getPVInfoVector(qr interface{}) (map[string]*PersistentVolumeData, error) {
 		if !ok {
 		if !ok {
 			return nil, fmt.Errorf("Metric field is improperly formatted")
 			return nil, fmt.Errorf("Metric field is improperly formatted")
 		}
 		}
-		pvclaim, ok := metricMap["persistentvolumeclaim"]
-		if !ok {
-			return nil, fmt.Errorf("Claim field does not exist in data result vector")
-		}
-		pvclaimStr, ok := pvclaim.(string)
-		if !ok {
-			return nil, fmt.Errorf("Claim field improperly formatted")
-		}
-		pvclass, ok := metricMap["storageclass"]
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field does not exist in data result vector")
-		}
-		pvclassStr, ok := pvclass.(string)
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field improperly formatted")
-		}
-		pvnamespace, ok := metricMap["namespace"]
-		if !ok {
-			return nil, fmt.Errorf("Namespace field does not exist in data result vector")
-		}
-		pvnamespaceStr, ok := pvnamespace.(string)
-		if !ok {
-			return nil, fmt.Errorf("StorageClass field improperly formatted")
-		}
+		pvclaim := metricMap["persistentvolumeclaim"]
+		pvclass := metricMap["storageclass"]
+		pvnamespace := metricMap["namespace"]
 		dataPoint, ok := val.(map[string]interface{})["value"]
 		dataPoint, ok := val.(map[string]interface{})["value"]
 		if !ok {
 		if !ok {
 			return nil, fmt.Errorf("Value field does not exist in data result vector")
 			return nil, fmt.Errorf("Value field does not exist in data result vector")
@@ -969,11 +959,11 @@ func getPVInfoVector(qr interface{}) (map[string]*PersistentVolumeData, error) {
 			Value:     v,
 			Value:     v,
 		})
 		})
 
 
-		key := pvnamespaceStr + "," + pvclaimStr
+		key := pvclaim.(string) + "," + pvnamespace.(string)
 		pvmap[key] = &PersistentVolumeData{
 		pvmap[key] = &PersistentVolumeData{
-			Class:     pvclassStr,
-			Claim:     pvclaimStr,
-			Namespace: pvnamespaceStr,
+			Class:     pvclass.(string),
+			Claim:     pvclaim.(string),
+			Namespace: pvnamespace.(string),
 			Values:    vectors,
 			Values:    vectors,
 		}
 		}
 	}
 	}

+ 25 - 0
main.go

@@ -123,6 +123,29 @@ func (p *Accesses) GetAllNodePricing(w http.ResponseWriter, r *http.Request, ps
 	w.Write(wrapData(data, err))
 	w.Write(wrapData(data, err))
 }
 }
 
 
+func (p *Accesses) GetConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+	data, err := p.Cloud.GetConfig()
+	w.Write(wrapData(data, err))
+}
+
+func (p *Accesses) UpdateConfigs(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*")
+	data, err := p.Cloud.UpdateConfig(r.Body)
+	if err != nil {
+		w.Write(wrapData(data, err))
+		return
+	}
+	w.Write(wrapData(data, err))
+	err = p.Cloud.DownloadPricingData()
+	if err != nil {
+		klog.V(1).Infof("Error redownloading data on config update")
+	}
+	return
+}
+
 func Healthz(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
 func Healthz(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
 	w.WriteHeader(200)
 	w.WriteHeader(200)
 	w.Header().Set("Content-Length", "0")
 	w.Header().Set("Content-Length", "0")
@@ -266,7 +289,9 @@ func main() {
 	router.GET("/outOfClusterCosts", a.OutofClusterCosts)
 	router.GET("/outOfClusterCosts", a.OutofClusterCosts)
 	router.GET("/allNodePricing", a.GetAllNodePricing)
 	router.GET("/allNodePricing", a.GetAllNodePricing)
 	router.GET("/healthz", Healthz)
 	router.GET("/healthz", Healthz)
+	router.GET("/getConfigs", a.GetConfigs)
 	router.POST("/refreshPricing", a.RefreshPricingData)
 	router.POST("/refreshPricing", a.RefreshPricingData)
+	router.POST("/updateConfigs", a.UpdateConfigs)
 
 
 	rootMux := http.NewServeMux()
 	rootMux := http.NewServeMux()
 	rootMux.Handle("/", router)
 	rootMux.Handle("/", router)