Просмотр исходного кода

Support external allocations by label aliases

Niko Kovacevic 4 лет назад
Родитель
Сommit
9447a529ac
3 измененных файлов с 86 добавлено и 10 удалено
  1. 27 3
      pkg/kubecost/asset.go
  2. 54 7
      pkg/kubecost/asset_test.go
  3. 5 0
      pkg/kubecost/config.go

+ 27 - 3
pkg/kubecost/asset.go

@@ -155,6 +155,9 @@ func AssetToExternalAllocation(asset Asset, aggregateBy []string, labelConfig *L
 	// external cost under "kubecost").
 	for _, aggBy := range aggregateBy {
 		name := labelConfig.GetExternalAllocationName(asset.Labels(), aggBy)
+
+		log.Infof("External: %s => %s", asset.Properties().Name, name)
+
 		if name == "" {
 			// No matching label has been defined in the cost-analyzer label config
 			// relating to the given aggregateBy property.
@@ -164,6 +167,11 @@ func AssetToExternalAllocation(asset Asset, aggregateBy []string, labelConfig *L
 			names = append(names, name)
 			match = true
 
+			// Default labels to an empty map, if necessary
+			if props.Labels == nil {
+				props.Labels = map[string]string{}
+			}
+
 			// Set the corresponding property on props
 			switch aggBy {
 			case AllocationClusterProp:
@@ -182,12 +190,28 @@ func AssetToExternalAllocation(asset Asset, aggregateBy []string, labelConfig *L
 				props.Container = name
 			case AllocationServiceProp:
 				props.Services = []string{name}
+			case AllocationDeploymentProp:
+				props.Controller = name
+				props.ControllerKind = "deployment"
+			case AllocationStatefulSetProp:
+				props.Controller = name
+				props.ControllerKind = "statefulset"
+			case AllocationDaemonSetProp:
+				props.Controller = name
+				props.ControllerKind = "daemonset"
+			case AllocationDepartmentProp:
+				props.Labels[labelConfig.DepartmentLabel] = name
+			case AllocationEnvironmentProp:
+				props.Labels[labelConfig.EnvironmentLabel] = name
+			case AllocationOwnerProp:
+				props.Labels[labelConfig.OwnerLabel] = name
+			case AllocationProductProp:
+				props.Labels[labelConfig.ProductLabel] = name
+			case AllocationTeamProp:
+				props.Labels[labelConfig.TeamLabel] = name
 			default:
 				if strings.HasPrefix(aggBy, "label:") {
 					// Set the corresponding label in props
-					if props.Labels == nil {
-						props.Labels = map[string]string{}
-					}
 					labelName := strings.TrimPrefix(aggBy, "label:")
 					labelValue := strings.TrimPrefix(name, labelName+"=")
 					props.Labels[labelName] = labelValue

+ 54 - 7
pkg/kubecost/asset_test.go

@@ -923,7 +923,9 @@ func TestAssetToExternalAllocation(t *testing.T) {
 	var alloc *Allocation
 	var err error
 
-	_, err = AssetToExternalAllocation(asset, []string{"namespace"}, nil)
+	labelConfig := NewLabelConfig()
+
+	_, err = AssetToExternalAllocation(asset, []string{"namespace"}, labelConfig)
 	if err == nil {
 		t.Fatalf("expected error due to nil asset; no error returned")
 	}
@@ -938,9 +940,14 @@ func TestAssetToExternalAllocation(t *testing.T) {
 	//   }
 	cloud := NewCloud(ComputeCategory, "abc123", start1, start2, windows[0])
 	cloud.SetLabels(map[string]string{
-		"kubernetes_namespace": "monitoring",
-		"env":                  "prod",
-		"app":                  "cost-analyzer",
+		"kubernetes_namespace":        "monitoring",
+		"env":                         "prod",
+		"app":                         "cost-analyzer",
+		"kubernetes_label_app":        "app",
+		"kubernetes_label_department": "department",
+		"kubernetes_label_env":        "env",
+		"kubernetes_label_owner":      "owner",
+		"kubernetes_label_team":       "team",
 	})
 	cloud.Cost = 10.00
 	asset = cloud
@@ -971,7 +978,12 @@ func TestAssetToExternalAllocation(t *testing.T) {
 	//   allocationPropertyLabels = {"namespace":"kubernetes_namespace"}
 	//   => Allocation{Name: "monitoring/__unallocated__", ExternalCost: 10.00, TotalCost: 10.00}, nil
 	//
-	//   4) no match
+	//	 4) label alias match(es)
+	//	 aggregateBy = ["product", "deployment", "environment", "owner", "team"]
+	//   allocationPropertyLabels = {"namespace":"kubernetes_namespace"}
+	//   => Allocation{Name: "app/department/env/owner/team", ExternalCost: 10.00, TotalCost: 10.00}, nil
+	//
+	//   5) no match
 	//   aggregateBy = ["cluster"]
 	//   allocationPropertyLabels = {"namespace":"kubernetes_namespace"}
 	//   => nil, err
@@ -1033,13 +1045,48 @@ func TestAssetToExternalAllocation(t *testing.T) {
 		t.Fatalf("expected external allocation with TotalCost %f; got %f", 10.00, alloc.TotalCost())
 	}
 
-	// 4) no match
-	alloc, err = AssetToExternalAllocation(asset, []string{"cluster"}, nil)
+	// 4) label alias match(es)
+	alloc, err = AssetToExternalAllocation(asset, []string{"product", "department", "environment", "owner", "team"}, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if alloc.Name != "app/department/env/owner/team/__external__" {
+		t.Fatalf("expected external allocation with name '%s'; got '%s'", "app/department/env/owner/team/__external__", alloc.Name)
+	}
+	if alloc.Properties.Labels[labelConfig.ProductLabel] != "app" {
+		t.Fatalf("expected external allocation with label %s equal to %s; got %s", labelConfig.ProductLabel, "app", alloc.Properties.Labels[labelConfig.ProductLabel])
+	}
+	if alloc.Properties.Labels[labelConfig.DepartmentLabel] != "department" {
+		t.Fatalf("expected external allocation with label %s equal to %s; got %s", labelConfig.DepartmentLabel, "department", alloc.Properties.Labels[labelConfig.DepartmentLabel])
+	}
+	if alloc.Properties.Labels[labelConfig.EnvironmentLabel] != "env" {
+		t.Fatalf("expected external allocation with label %s equal to %s; got %s", labelConfig.EnvironmentLabel, "env", alloc.Properties.Labels[labelConfig.EnvironmentLabel])
+	}
+	if alloc.Properties.Labels[labelConfig.OwnerLabel] != "owner" {
+		t.Fatalf("expected external allocation with label %s equal to %s; got %s", labelConfig.OwnerLabel, "owner", alloc.Properties.Labels[labelConfig.OwnerLabel])
+	}
+	if alloc.Properties.Labels[labelConfig.TeamLabel] != "team" {
+		t.Fatalf("expected external allocation with label %s equal to %s; got %s", labelConfig.TeamLabel, "team", alloc.Properties.Labels[labelConfig.TeamLabel])
+	}
+	if alloc.ExternalCost != 10.00 {
+		t.Fatalf("expected external allocation with ExternalCost %f; got %f", 10.00, alloc.ExternalCost)
+	}
+	if alloc.TotalCost() != 10.00 {
+		t.Fatalf("expected external allocation with TotalCost %f; got %f", 10.00, alloc.TotalCost())
+	}
+
+	// 5) no match
+	_, err = AssetToExternalAllocation(asset, []string{"cluster"}, nil)
 	if err == nil {
 		t.Fatalf("expected 'no match' error")
 	}
 
+	// other cases
+
 	alloc, err = AssetToExternalAllocation(asset, []string{"namespace", "label:app"}, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
 	if alloc.ExternalCost != 10.00 {
 		t.Fatalf("expected external allocation with ExternalCost %f; got %f", 10.00, alloc.ExternalCost)
 	}

+ 5 - 0
pkg/kubecost/config.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/kubecost/cost-model/pkg/log"
 	"github.com/kubecost/cost-model/pkg/prom"
 	"github.com/kubecost/cost-model/pkg/util/cloudutil"
 )
@@ -234,6 +235,8 @@ func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggre
 		}
 	}
 
+	log.Infof("External: aggregate by %s: labelNames: %v", aggregateBy, labelNames)
+
 	// No label is set for the given aggregation property.
 	if len(labelNames) == 0 {
 		return ""
@@ -262,6 +265,8 @@ func (lc *LabelConfig) GetExternalAllocationName(labels map[string]string, aggre
 		}
 	}
 
+	log.Infof("External: aggregate by %s: %s = %s", aggregateBy, labelName, labelValue)
+
 	// No match found
 	if labelName == "" {
 		return ""