Browse Source

Nodepool percentage (#1850)

* Add a new Assets API handler to return node counts

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* derive nodepool label

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* code review comments - rename file, re-impl node pool name derivation

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* generate bingen codecs

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* updates to restore missing function

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* compute pool in return object only

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

* resolve import cycle, rem further bingen items

Signed-off-by: Alex Meijer <ameijer@kubecost.com>

---------

Signed-off-by: Alex Meijer <ameijer@kubecost.com>
Alex Meijer 3 years ago
parent
commit
3248b243c3

+ 1 - 0
pkg/cloud/awsprovider.go

@@ -60,6 +60,7 @@ const (
 	AWSHourlyPublicIPCost    = 0.005
 	EKSCapacityTypeLabel     = "eks.amazonaws.com/capacityType"
 	EKSCapacitySpotTypeValue = "SPOT"
+	
 )
 
 var (

+ 1 - 0
pkg/cloud/gcpprovider.go

@@ -46,6 +46,7 @@ const (
 
 	GKEPreemptibleLabel = "cloud.google.com/gke-preemptible"
 	GKESpotLabel        = "cloud.google.com/gke-spot"
+	
 )
 
 // List obtained by installing the `gcloud` CLI tool,

+ 0 - 1
pkg/cloud/scalewayprovider.go

@@ -49,7 +49,6 @@ type Scaleway struct {
 func (c *Scaleway) PricingSourceSummary() interface{} {
 	return c.Pricing
 }
-
 func (c *Scaleway) DownloadPricingData() error {
 	c.DownloadPricingDataLock.Lock()
 	defer c.DownloadPricingDataLock.Unlock()

+ 1 - 0
pkg/cmd/costmodel/costmodel.go

@@ -43,6 +43,7 @@ func Execute(opts *CostModelOpts) error {
 	a.Router.GET("/healthz", Healthz)
 	a.Router.GET("/allocation", a.ComputeAllocationHandler)
 	a.Router.GET("/allocation/summary", a.ComputeAllocationHandlerSummary)
+	a.Router.GET("/assets", a.ComputeAssetsHandler)
 	rootMux.Handle("/", a.Router)
 	rootMux.Handle("/metrics", promhttp.Handler())
 	telemetryHandler := metrics.ResponseMetricMiddleware(rootMux)

+ 34 - 0
pkg/costmodel/handlers.go

@@ -0,0 +1,34 @@
+package costmodel
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/julienschmidt/httprouter"
+	"github.com/opencost/opencost/pkg/env"
+	"github.com/opencost/opencost/pkg/kubecost"
+	"github.com/opencost/opencost/pkg/util/httputil"
+)
+
+// ComputeAllocationHandler returns the assets from the CostModel.
+func (a *Accesses) ComputeAssetsHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
+	w.Header().Set("Content-Type", "application/json")
+
+	qp := httputil.NewQueryParams(r.URL.Query())
+
+	// Window is a required field describing the window of time over which to
+	// compute allocation data.
+	window, err := kubecost.ParseWindowWithOffset(qp.Get("window", ""), env.GetParsedUTCOffset())
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Invalid 'window' parameter: %s", err), http.StatusBadRequest)
+		return
+	}
+
+	assetSet, err := a.Model.ComputeAssets(*window.Start(), *window.End())
+	if err != nil {
+		http.Error(w, fmt.Sprintf("Error computing asset set: %s", err), http.StatusInternalServerError)
+		return
+	}
+
+	w.Write(WrapData(assetSet, nil))
+}

+ 1 - 1
pkg/kubecost/allocationprops.go

@@ -105,7 +105,7 @@ type AllocationProperties struct {
 	Annotations    AllocationAnnotations `json:"annotations,omitempty"`
 	// When set to true, maintain the intersection of all labels + annotations
 	// in the aggregated AllocationProperties object
-	AggregatedMetadata bool `json:"-"`
+	AggregatedMetadata bool `json:"-"` //@bingen:field[ignore]
 }
 
 // AllocationLabels is a schema-free mapping of key/value pairs that can be

+ 26 - 0
pkg/kubecost/asset.go

@@ -4,6 +4,7 @@ import (
 	"encoding"
 	"fmt"
 	"math"
+	"regexp"
 	"strings"
 	"time"
 
@@ -3792,3 +3793,28 @@ func contains(slice []string, item string) bool {
 	}
 	return false
 }
+
+func GetNodePoolName(provider string, labels map[string]string) string {
+
+	switch provider {
+	case AzureProvider:
+		return getPoolNameHelper(AKSNodepoolLabel, labels)
+	case AWSProvider:
+		return getPoolNameHelper(EKSNodepoolLabel, labels)
+	case GCPProvider:
+		return getPoolNameHelper(GKENodePoolLabel, labels)
+	default:
+		log.Warnf("node pool name not supported for this provider")
+		return ""
+	}
+}
+
+func getPoolNameHelper(label string, labels map[string]string) string {
+	sanitizedLabel := regexp.MustCompile(`[^a-zA-Z0-9 ]+`).ReplaceAllString(label, "_")
+	if poolName, found := labels[fmt.Sprintf("label_%s", sanitizedLabel)]; found {
+		return poolName
+	} else {
+		log.Warnf("unable to derive node pool name from node labels")
+		return ""
+	}
+}

+ 4 - 0
pkg/kubecost/asset_json.go

@@ -477,6 +477,9 @@ func (n *Node) MarshalJSON() ([]byte, error) {
 	jsonEncodeString(buffer, "end", n.End.Format(time.RFC3339), ",")
 	jsonEncodeFloat64(buffer, "minutes", n.Minutes(), ",")
 	jsonEncodeString(buffer, "nodeType", n.NodeType, ",")
+	if poolName := GetNodePoolName(n.Properties.Provider, n.Labels); poolName != "" {
+		jsonEncodeString(buffer, "pool", poolName, ",")
+	}
 	jsonEncodeFloat64(buffer, "cpuCores", n.CPUCores(), ",")
 	jsonEncodeFloat64(buffer, "ramBytes", n.RAMBytes(), ",")
 	jsonEncodeFloat64(buffer, "cpuCoreHours", n.CPUCoreHours, ",")
@@ -561,6 +564,7 @@ func (n *Node) InterfaceToNode(itf interface{}) error {
 	if NodeType, err := getTypedVal(fmap["nodeType"]); err == nil {
 		n.NodeType = NodeType.(string)
 	}
+	
 	if CPUCoreHours, err := getTypedVal(fmap["cpuCoreHours"]); err == nil {
 		n.CPUCoreHours = CPUCoreHours.(float64)
 	}

+ 9 - 0
pkg/kubecost/assetprops.go

@@ -118,12 +118,21 @@ const OtherCategory = "Other"
 // AWSProvider describes the provider AWS
 const AWSProvider = "AWS"
 
+// describes how AWS labels nodepool nodes
+const EKSNodepoolLabel = "eks.amazonaws.com/nodegroup"
+
 // GCPProvider describes the provider GCP
 const GCPProvider = "GCP"
 
+// describes how nodepool nodes are labeled in GKE
+const GKENodePoolLabel = "cloud.google.com/gke-nodepool"
+
 // AzureProvider describes the provider Azure
 const AzureProvider = "Azure"
 
+// describes how Azure labels nodepool nodes
+const AKSNodepoolLabel = "kubernetes.azure.com/agentpool"
+
 // AlibabaProvider describes the provider for Alibaba Cloud
 const AlibabaProvider = "Alibaba"