2
0
Эх сурвалжийг харах

Merge pull request #1248 from kubecost/nick/diff-asset

Initial feature for diffing Assets + test cases
Nick Curie 3 жил өмнө
parent
commit
040df8955b
4 өөрчлөгдсөн 161 нэмэгдсэн , 8 устгасан
  1. 5 4
      go.mod
  2. 10 4
      go.sum
  3. 38 0
      pkg/kubecost/asset.go
  4. 108 0
      pkg/kubecost/diff_test.go

+ 5 - 4
go.mod

@@ -42,6 +42,7 @@ require (
 	github.com/spf13/cobra v1.2.1
 	github.com/spf13/viper v1.8.1
 	go.etcd.io/bbolt v1.3.5
+	golang.org/x/exp v0.0.0-20220609121020-a51bd0440498
 	golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/api v0.44.0
@@ -116,13 +117,13 @@ require (
 	go.opencensus.io v0.23.0 // indirect
 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
 	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
-	golang.org/x/mod v0.4.2 // indirect
+	golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
 	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
-	golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect
+	golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
 	golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
-	golang.org/x/tools v0.1.7 // indirect
+	golang.org/x/tools v0.1.10 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect

+ 10 - 4
go.sum

@@ -624,6 +624,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220609121020-a51bd0440498 h1:TF0FvLUGEq/8wOt/9AV1nj6D4ViZGUIGCMQfCv7VRXY=
+golang.org/x/exp v0.0.0-20220609121020-a51bd0440498/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -649,8 +651,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
+golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -779,8 +782,9 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -791,8 +795,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -853,8 +858,9 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
 golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
+golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

+ 38 - 0
pkg/kubecost/asset.go

@@ -2838,6 +2838,44 @@ func (as *AssetSet) accumulate(that *AssetSet) (*AssetSet, error) {
 	return acc, nil
 }
 
+
+type DiffKind string
+
+const (
+    DiffAdded DiffKind = "added"
+    DiffRemoved = "removed"
+)
+
+// Diff stores an object and a string that denotes whether that object was
+// added or removed from a set of those objects
+type Diff[T any] struct {
+	Entity T
+	Kind DiffKind
+}
+
+// DiffAsset takes two AssetSets and returns a slice of Diffs by checking
+// the keys of each AssetSet. If a key is not found, a Diff is generated
+// and added to the slice.
+func DiffAsset(before, after *AssetSet) []Diff[Asset]{
+	changedItems := []Diff[Asset]{}
+
+	for assetKey1, asset1 := range before.assets {
+		if _, ok := after.assets[assetKey1]; !ok {
+			d := Diff[Asset]{asset1, DiffRemoved}
+			changedItems = append(changedItems, d)
+		}
+	}
+	
+	for assetKey2, asset2 := range after.assets {
+		if _, ok := before.assets[assetKey2]; !ok {
+			d := Diff[Asset]{asset2, DiffAdded}
+			changedItems = append(changedItems, d)
+		}
+	}
+
+	return changedItems
+}
+
 // AssetSetRange is a thread-safe slice of AssetSets. It is meant to
 // be used such that the AssetSets held are consecutive and coherent with
 // respect to using the same aggregation properties, UTC offset, and

+ 108 - 0
pkg/kubecost/diff_test.go

@@ -0,0 +1,108 @@
+package kubecost
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"golang.org/x/exp/slices"
+)
+
+func TestDiff(t *testing.T) {
+
+	start := time.Now().AddDate(0, 0, -1)
+	end :=  time.Now()
+	window1 := NewWindow(&start, &end)
+
+	node1 := NewNode("node1", "cluster1", "123abc", start, end, window1)
+	node2 := NewNode("node2", "cluster1", "123abc", start, end, window1)
+	node3 := NewNode("node3", "cluster1", "123abc", start, end, window1)
+	node4 := NewNode("node4", "cluster1", "123abc", start, end, window1)
+	disk1 := NewDisk("disk1", "cluster1", "123abc", start, end, window1)
+	disk2 := NewDisk("disk2", "cluster1", "123abc", start, end, window1)
+
+	cases := map[string]struct {
+		inputAssetsBefore []Asset
+		inputAssetsAfter  []Asset
+		expected          []Diff[Asset]
+	}{
+		"added node":            {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{node1, node2, node3}, 
+			expected:          []Diff[Asset]{{node3, DiffAdded}},
+		},
+		"multiple adds":         {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{node1, node2, node3, node4}, 
+			expected:          []Diff[Asset]{{node3, DiffAdded}, {node4, DiffAdded}},
+		},
+		"removed node":          {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{node2}, 
+			expected:          []Diff[Asset]{{node1, DiffRemoved}},
+		},
+		"multiple removes":      {
+			inputAssetsBefore: []Asset{node1, node2, node3}, 
+			inputAssetsAfter:  []Asset{node2}, 
+			expected:          []Diff[Asset]{{node1, DiffRemoved}, {node3, DiffRemoved}},
+		},
+		"remove all":            {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{}, 
+			expected:          []Diff[Asset]{{node1, DiffRemoved}, {node2, DiffRemoved}},
+		},
+		"add and remove":        {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{node2, node3}, 
+			expected:          []Diff[Asset]{{node1, DiffRemoved}, {node3, DiffAdded}},
+		},
+		"no change":             {
+			inputAssetsBefore: []Asset{node1, node2}, 
+			inputAssetsAfter:  []Asset{node1, node2}, 
+			expected:          []Diff[Asset]{},
+		},
+		"order switch":          {
+			inputAssetsBefore: []Asset{node2, node1}, 
+			inputAssetsAfter:  []Asset{node1, node2}, 
+			expected:          []Diff[Asset]{},
+		},
+		"disk add":              {
+			inputAssetsBefore: []Asset{disk1, node1}, 
+			inputAssetsAfter:  []Asset{disk1, node1, disk2}, 
+			expected:          []Diff[Asset]{{disk2, DiffAdded}},
+		},
+		"disk and node add":     {
+			inputAssetsBefore: []Asset{disk1, node1}, 
+			inputAssetsAfter:  []Asset{disk1, node1, disk2, node2}, 
+			expected:          []Diff[Asset]{{disk2, DiffAdded}, {node2, DiffAdded}},
+		},
+		"disk and node removed": {
+			inputAssetsBefore: []Asset{disk1, node1, disk2, node2}, 
+			inputAssetsAfter:  []Asset{disk2, node2}, 
+			expected:          []Diff[Asset]{{disk1, DiffRemoved}, {node1, DiffRemoved}},
+		},
+	}
+
+	for name, tc := range cases {
+		t.Run(name, func(t *testing.T) {
+			as1 := NewAssetSet(start, end, tc.inputAssetsBefore...)
+			as2 := NewAssetSet(start, end, tc.inputAssetsAfter...)
+
+			result := DiffAsset(as1.Clone(), as2.Clone())
+
+			slices.SortFunc(result, func(a, b Diff[Asset]) bool {
+				return a.Entity.Properties().Name < b.Entity.Properties().Name
+			})
+
+			slices.SortFunc(tc.expected, func(a, b Diff[Asset]) bool {
+				return a.Entity.Properties().Name < b.Entity.Properties().Name
+			})
+	
+			if !reflect.DeepEqual(result, tc.expected) {
+				t.Fatalf("expected %+v; got %+v", tc.expected, result)
+			}
+			
+		})
+	}
+
+}