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

Merge pull request #2701 from Sean-Holcomb/v2.2-Patch/hash-cc-keys

Add hash key for saving cloud cost
Sean Holcomb 2 лет назад
Родитель
Сommit
088f891d8e
2 измененных файлов с 91 добавлено и 1 удалено
  1. 29 1
      core/pkg/opencost/cloudcostprops.go
  2. 62 0
      core/pkg/opencost/cloudcostprops_test.go

+ 29 - 1
core/pkg/opencost/cloudcostprops.go

@@ -1,10 +1,14 @@
 package opencost
 
 import (
+	"encoding/hex"
 	"fmt"
+	"hash/fnv"
+	"sort"
 	"strings"
 
 	"github.com/opencost/opencost/core/pkg/log"
+	"golang.org/x/exp/maps"
 )
 
 type CloudCostProperty string
@@ -31,6 +35,7 @@ const (
 	CloudCostCategoryProp        string = "category"
 	CloudCostServiceProp         string = "service"
 	CloudCostLabelProp           string = "label"
+	CloudCostLabelSetProp        string = "labelSet"
 )
 
 func ParseCloudProperties(props []string) ([]CloudCostProperty, error) {
@@ -230,7 +235,7 @@ func (ccp *CloudCostProperties) GenerateKey(props []string) string {
 
 	// nil props replaced with default property list
 	if props == nil {
-		props = cloudCostDefaultKeyProperties
+		return ccp.hashKey()
 	}
 
 	values := make([]string, len(props))
@@ -283,3 +288,26 @@ func (ccp *CloudCostProperties) GenerateKey(props []string) string {
 
 	return strings.Join(values, "/")
 }
+
+// HashKey creates a key on the entire property set including labels of a uniform length.
+// This key is meant to be used when constructing unaggregated CloudCostSet for storage
+func (ccp *CloudCostProperties) hashKey() string {
+	builder := strings.Builder{}
+	builder.WriteString(ccp.ProviderID)
+	builder.WriteString(ccp.Provider)
+	builder.WriteString(ccp.AccountID)
+	builder.WriteString(ccp.InvoiceEntityID)
+	builder.WriteString(ccp.Service)
+	builder.WriteString(ccp.Category)
+
+	// Sort label keys before adding key/value pairs to the hash string
+	labelKeys := maps.Keys(ccp.Labels)
+	sort.Strings(labelKeys)
+	for _, k := range labelKeys {
+		builder.WriteString(k)
+		builder.WriteString(ccp.Labels[k])
+	}
+	hasher := fnv.New64a()
+	hasher.Write([]byte(builder.String()))
+	return hex.EncodeToString(hasher.Sum(nil))
+}

+ 62 - 0
core/pkg/opencost/cloudcostprops_test.go

@@ -163,3 +163,65 @@ func TestCloudCostPropertiesIntersection(t *testing.T) {
 		})
 	}
 }
+
+func TestCloudCostProperties_hashKey(t *testing.T) {
+
+	tests := map[string]struct {
+		props *CloudCostProperties
+		want  string
+	}{
+		"enpty props": {
+			props: &CloudCostProperties{},
+			want:  "cbf29ce484222325",
+		},
+		"All props no labels": {
+			props: &CloudCostProperties{
+				ProviderID:      "providerid1",
+				Provider:        "provider1",
+				AccountID:       "workgroup1",
+				InvoiceEntityID: "billing1",
+				Service:         "service1",
+				Category:        "category1",
+				Labels:          map[string]string{},
+			},
+			want: "a19b7dddf0032572",
+		},
+		"All props": {
+			props: &CloudCostProperties{
+				ProviderID:      "providerid1",
+				Provider:        "provider1",
+				AccountID:       "workgroup1",
+				InvoiceEntityID: "billing1",
+				Service:         "service1",
+				Category:        "category1",
+				Labels: map[string]string{
+					"label1": "value1",
+					"label2": "value2",
+				},
+			},
+			want: "9d54403e40ad4db6",
+		},
+		"All props swap labels": {
+			props: &CloudCostProperties{
+				ProviderID:      "providerid1",
+				Provider:        "provider1",
+				AccountID:       "workgroup1",
+				InvoiceEntityID: "billing1",
+				Service:         "service1",
+				Category:        "category1",
+				Labels: map[string]string{
+					"label2": "value2",
+					"label1": "value1",
+				},
+			},
+			want: "9d54403e40ad4db6",
+		},
+	}
+	for name, tt := range tests {
+		t.Run(name, func(t *testing.T) {
+			if got := tt.props.hashKey(); got != tt.want {
+				t.Errorf("hashKey() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}