Sfoglia il codice sorgente

Sth/kcm 5687 (#3783)

Signed-off-by: Sean Holcomb <seanholcomb@gmail.com>
Sean Holcomb 5 giorni fa
parent
commit
cad1176fe7

+ 49 - 0
core/pkg/clustercache/mock.go

@@ -0,0 +1,49 @@
+package clustercache
+
+// MockClusterCache is a configurable implementation of ClusterCache for use in tests.
+// Each field corresponds to the slice returned by the matching GetAll* method.
+// Any field left nil causes its method to return nil.
+type MockClusterCache struct {
+	Nodes                  []*Node
+	Pods                   []*Pod
+	Namespaces             []*Namespace
+	Services               []*Service
+	DaemonSets             []*DaemonSet
+	Deployments            []*Deployment
+	StatefulSets           []*StatefulSet
+	ReplicaSets            []*ReplicaSet
+	PersistentVolumes      []*PersistentVolume
+	PersistentVolumeClaims []*PersistentVolumeClaim
+	StorageClasses         []*StorageClass
+	Jobs                   []*Job
+	CronJobs               []*CronJob
+	PodDisruptionBudgets   []*PodDisruptionBudget
+	ReplicationControllers []*ReplicationController
+	ResourceQuotas         []*ResourceQuota
+}
+
+func (m *MockClusterCache) Run()  {}
+func (m *MockClusterCache) Stop() {}
+
+func (m *MockClusterCache) GetAllNodes() []*Node                           { return m.Nodes }
+func (m *MockClusterCache) GetAllPods() []*Pod                             { return m.Pods }
+func (m *MockClusterCache) GetAllNamespaces() []*Namespace                 { return m.Namespaces }
+func (m *MockClusterCache) GetAllServices() []*Service                     { return m.Services }
+func (m *MockClusterCache) GetAllDaemonSets() []*DaemonSet                 { return m.DaemonSets }
+func (m *MockClusterCache) GetAllDeployments() []*Deployment               { return m.Deployments }
+func (m *MockClusterCache) GetAllStatefulSets() []*StatefulSet             { return m.StatefulSets }
+func (m *MockClusterCache) GetAllReplicaSets() []*ReplicaSet               { return m.ReplicaSets }
+func (m *MockClusterCache) GetAllPersistentVolumes() []*PersistentVolume   { return m.PersistentVolumes }
+func (m *MockClusterCache) GetAllPersistentVolumeClaims() []*PersistentVolumeClaim {
+	return m.PersistentVolumeClaims
+}
+func (m *MockClusterCache) GetAllStorageClasses() []*StorageClass { return m.StorageClasses }
+func (m *MockClusterCache) GetAllJobs() []*Job                    { return m.Jobs }
+func (m *MockClusterCache) GetAllCronJobs() []*CronJob            { return m.CronJobs }
+func (m *MockClusterCache) GetAllPodDisruptionBudgets() []*PodDisruptionBudget {
+	return m.PodDisruptionBudgets
+}
+func (m *MockClusterCache) GetAllReplicationControllers() []*ReplicationController {
+	return m.ReplicationControllers
+}
+func (m *MockClusterCache) GetAllResourceQuotas() []*ResourceQuota { return m.ResourceQuotas }

+ 15 - 0
core/pkg/source/decoders.go

@@ -248,6 +248,7 @@ func DecodeLocalStorageActiveMinutesResult(result *QueryResult) *LocalStorageAct
 }
 
 type LocalStorageCostResult struct {
+	UID      string
 	Cluster  string
 	Instance string
 	Device   string
@@ -256,11 +257,13 @@ type LocalStorageCostResult struct {
 }
 
 func DecodeLocalStorageCostResult(result *QueryResult) *LocalStorageCostResult {
+	uid, _ := result.GetString(UIDLabel)
 	cluster, _ := result.GetCluster()
 	instance, _ := result.GetInstance()
 	device, _ := result.GetDevice()
 
 	return &LocalStorageCostResult{
+		UID:      uid,
 		Cluster:  cluster,
 		Instance: instance,
 		Device:   device,
@@ -269,6 +272,7 @@ func DecodeLocalStorageCostResult(result *QueryResult) *LocalStorageCostResult {
 }
 
 type LocalStorageUsedCostResult struct {
+	UID      string
 	Cluster  string
 	Instance string
 	Device   string
@@ -276,11 +280,13 @@ type LocalStorageUsedCostResult struct {
 }
 
 func DecodeLocalStorageUsedCostResult(result *QueryResult) *LocalStorageUsedCostResult {
+	uid, _ := result.GetString(UIDLabel)
 	cluster, _ := result.GetCluster()
 	instance, _ := result.GetInstance()
 	device, _ := result.GetDevice()
 
 	return &LocalStorageUsedCostResult{
+		UID:      uid,
 		Cluster:  cluster,
 		Instance: instance,
 		Device:   device,
@@ -289,6 +295,7 @@ func DecodeLocalStorageUsedCostResult(result *QueryResult) *LocalStorageUsedCost
 }
 
 type LocalStorageUsedAvgResult struct {
+	UID      string
 	Cluster  string
 	Instance string
 	Device   string
@@ -296,11 +303,13 @@ type LocalStorageUsedAvgResult struct {
 }
 
 func DecodeLocalStorageUsedAvgResult(result *QueryResult) *LocalStorageUsedAvgResult {
+	uid, _ := result.GetString(UIDLabel)
 	cluster, _ := result.GetCluster()
 	instance, _ := result.GetInstance()
 	device, _ := result.GetDevice()
 
 	return &LocalStorageUsedAvgResult{
+		UID:      uid,
 		Cluster:  cluster,
 		Instance: instance,
 		Device:   device,
@@ -309,6 +318,7 @@ func DecodeLocalStorageUsedAvgResult(result *QueryResult) *LocalStorageUsedAvgRe
 }
 
 type LocalStorageUsedMaxResult struct {
+	UID      string
 	Cluster  string
 	Instance string
 	Device   string
@@ -316,11 +326,13 @@ type LocalStorageUsedMaxResult struct {
 }
 
 func DecodeLocalStorageUsedMaxResult(result *QueryResult) *LocalStorageUsedMaxResult {
+	uid, _ := result.GetString(UIDLabel)
 	cluster, _ := result.GetCluster()
 	instance, _ := result.GetInstance()
 	device, _ := result.GetDevice()
 
 	return &LocalStorageUsedMaxResult{
+		UID:      uid,
 		Cluster:  cluster,
 		Instance: instance,
 		Device:   device,
@@ -329,6 +341,7 @@ func DecodeLocalStorageUsedMaxResult(result *QueryResult) *LocalStorageUsedMaxRe
 }
 
 type LocalStorageBytesResult struct {
+	UID      string
 	Cluster  string
 	Instance string
 	Device   string
@@ -336,11 +349,13 @@ type LocalStorageBytesResult struct {
 }
 
 func DecodeLocalStorageBytesResult(result *QueryResult) *LocalStorageBytesResult {
+	uid, _ := result.GetString(UIDLabel)
 	cluster, _ := result.GetCluster()
 	instance, _ := result.GetInstance()
 	device, _ := result.GetDevice()
 
 	return &LocalStorageBytesResult{
+		UID:      uid,
 		Cluster:  cluster,
 		Instance: instance,
 		Device:   device,

+ 3 - 0
modules/collector-source/go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/kubecost/events v0.0.8
 	github.com/opencost/opencost/core v0.0.0-20250521155634-81d2b597d1bc
+	github.com/stretchr/testify v1.11.1
 	golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
 	k8s.io/api v0.36.0
 	k8s.io/apimachinery v0.36.0
@@ -86,6 +87,7 @@ require (
 	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
 	github.com/prometheus/common v0.67.5 // indirect
 	github.com/rs/xid v1.6.0 // indirect
@@ -126,6 +128,7 @@ require (
 	google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	k8s.io/client-go v0.36.0 // indirect
 	k8s.io/klog/v2 v2.140.0 // indirect
 	k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect

+ 4 - 25
modules/collector-source/pkg/scrape/clustercache.go

@@ -34,11 +34,6 @@ func newClusterCacheScraper(clusterCache clustercache.ClusterCache) Scraper {
 	}
 }
 
-type pvcKey struct {
-	name      string
-	namespace string
-}
-
 func (ccs *ClusterCacheScraper) Scrape() []metric.Update {
 	// retrieve objects for scrape
 	nodes := ccs.clusterCache.GetAllNodes()
@@ -57,26 +52,10 @@ func (ccs *ClusterCacheScraper) Scrape() []metric.Update {
 
 	// create scrape indexes. While the pairs being mapped here don't have a 1 to 1 relationship in the general case,
 	// we are assuming that in the context of a single snapshot of the cluster they are 1 to 1.
-	nodeNameToUID := make(map[string]types.UID, len(nodes))
-	for _, node := range nodes {
-		nodeNameToUID[node.Name] = node.UID
-	}
-	namespaceNameToUID := make(map[string]types.UID, len(namespaces))
-	for _, ns := range namespaces {
-		namespaceNameToUID[ns.Name] = ns.UID
-	}
-	pvcNameToUID := make(map[pvcKey]types.UID, len(pvcs))
-	for _, pvc := range pvcs {
-		key := pvcKey{
-			name:      pvc.Name,
-			namespace: pvc.Namespace,
-		}
-		pvcNameToUID[key] = pvc.UID
-	}
-	pvNameToUID := make(map[string]types.UID, len(pvs))
-	for _, pv := range pvs {
-		pvNameToUID[pv.Name] = pv.UID
-	}
+	nodeNameToUID := buildNodeIndex(nodes)
+	namespaceNameToUID := buildNamespaceIndex(namespaces)
+	pvcNameToUID := buildPVCIndex(pvcs)
+	pvNameToUID := buildPVIndex(pvs)
 
 	scrapeFuncs := []ScrapeFunc{
 		ccs.GetScrapeNodes(nodes),

+ 48 - 0
modules/collector-source/pkg/scrape/index.go

@@ -0,0 +1,48 @@
+package scrape
+
+import (
+	"github.com/opencost/opencost/core/pkg/clustercache"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+// pvcKey is a composite key for a PersistentVolumeClaim (name + namespace).
+type pvcKey struct {
+	name      string
+	namespace string
+}
+
+// buildNodeIndex returns a map from node name to UID.
+func buildNodeIndex(nodes []*clustercache.Node) map[string]types.UID {
+	m := make(map[string]types.UID, len(nodes))
+	for _, node := range nodes {
+		m[node.Name] = node.UID
+	}
+	return m
+}
+
+// buildNamespaceIndex returns a map from namespace name to UID.
+func buildNamespaceIndex(namespaces []*clustercache.Namespace) map[string]types.UID {
+	m := make(map[string]types.UID, len(namespaces))
+	for _, ns := range namespaces {
+		m[ns.Name] = ns.UID
+	}
+	return m
+}
+
+// buildPVCIndex returns a map from (name, namespace) to PVC UID.
+func buildPVCIndex(pvcs []*clustercache.PersistentVolumeClaim) map[pvcKey]types.UID {
+	m := make(map[pvcKey]types.UID, len(pvcs))
+	for _, pvc := range pvcs {
+		m[pvcKey{name: pvc.Name, namespace: pvc.Namespace}] = pvc.UID
+	}
+	return m
+}
+
+// buildPVIndex returns a map from PV name to UID.
+func buildPVIndex(pvs []*clustercache.PersistentVolume) map[string]types.UID {
+	m := make(map[string]types.UID, len(pvs))
+	for _, pv := range pvs {
+		m[pv.Name] = pv.UID
+	}
+	return m
+}

+ 75 - 0
modules/collector-source/pkg/scrape/index_test.go

@@ -0,0 +1,75 @@
+package scrape
+
+import (
+	"testing"
+
+	"github.com/opencost/opencost/core/pkg/clustercache"
+	"github.com/stretchr/testify/require"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+func TestBuildNodeIndex_Empty(t *testing.T) {
+	m := buildNodeIndex(nil)
+	require.Empty(t, m)
+}
+
+func TestBuildNodeIndex(t *testing.T) {
+	nodes := []*clustercache.Node{
+		{Name: "node-a", UID: "uid-a"},
+		{Name: "node-b", UID: "uid-b"},
+	}
+	m := buildNodeIndex(nodes)
+	require.Equal(t, types.UID("uid-a"), m["node-a"])
+	require.Equal(t, types.UID("uid-b"), m["node-b"])
+	require.Len(t, m, 2)
+}
+
+func TestBuildNamespaceIndex_Empty(t *testing.T) {
+	m := buildNamespaceIndex(nil)
+	require.Empty(t, m)
+}
+
+func TestBuildNamespaceIndex(t *testing.T) {
+	namespaces := []*clustercache.Namespace{
+		{Name: "default", UID: "uid-default"},
+		{Name: "kube-system", UID: "uid-kube-system"},
+	}
+	m := buildNamespaceIndex(namespaces)
+	require.Equal(t, types.UID("uid-default"), m["default"])
+	require.Equal(t, types.UID("uid-kube-system"), m["kube-system"])
+	require.Len(t, m, 2)
+}
+
+func TestBuildPVCIndex_Empty(t *testing.T) {
+	m := buildPVCIndex(nil)
+	require.Empty(t, m)
+}
+
+func TestBuildPVCIndex(t *testing.T) {
+	pvcs := []*clustercache.PersistentVolumeClaim{
+		{Name: "pvc-a", Namespace: "ns-1", UID: "uid-pvc-a"},
+		{Name: "pvc-a", Namespace: "ns-2", UID: "uid-pvc-a-ns2"}, // same name, different namespace
+		{Name: "pvc-b", Namespace: "ns-1", UID: "uid-pvc-b"},
+	}
+	m := buildPVCIndex(pvcs)
+	require.Equal(t, types.UID("uid-pvc-a"), m[pvcKey{name: "pvc-a", namespace: "ns-1"}])
+	require.Equal(t, types.UID("uid-pvc-a-ns2"), m[pvcKey{name: "pvc-a", namespace: "ns-2"}])
+	require.Equal(t, types.UID("uid-pvc-b"), m[pvcKey{name: "pvc-b", namespace: "ns-1"}])
+	require.Len(t, m, 3)
+}
+
+func TestBuildPVIndex_Empty(t *testing.T) {
+	m := buildPVIndex(nil)
+	require.Empty(t, m)
+}
+
+func TestBuildPVIndex(t *testing.T) {
+	pvs := []*clustercache.PersistentVolume{
+		{Name: "pv-a", UID: "uid-pv-a"},
+		{Name: "pv-b", UID: "uid-pv-b"},
+	}
+	m := buildPVIndex(pvs)
+	require.Equal(t, types.UID("uid-pv-a"), m["pv-a"])
+	require.Equal(t, types.UID("uid-pv-b"), m["pv-b"])
+	require.Len(t, m, 2)
+}

+ 1 - 1
modules/collector-source/pkg/scrape/scrapecontroller.go

@@ -41,7 +41,7 @@ func NewScrapeController(
 	opencostScraper := newOpenCostScraper()
 	scrapers = append(scrapers, opencostScraper)
 
-	statSummaryScraper := newStatSummaryScraper(statSummaryClient)
+	statSummaryScraper := newStatSummaryScraper(statSummaryClient, clusterCache)
 	scrapers = append(scrapers, statSummaryScraper)
 
 	networkScraper := newNetworkScraper(networkPort, clusterCache)

+ 21 - 3
modules/collector-source/pkg/scrape/statsummary.go

@@ -2,6 +2,7 @@ package scrape
 
 import (
 	"github.com/kubecost/events"
+	"github.com/opencost/opencost/core/pkg/clustercache"
 	"github.com/opencost/opencost/core/pkg/log"
 	"github.com/opencost/opencost/core/pkg/nodestats"
 	"github.com/opencost/opencost/core/pkg/source"
@@ -11,16 +12,22 @@ import (
 )
 
 type StatSummaryScraper struct {
-	client nodestats.StatSummaryClient
+	client       nodestats.StatSummaryClient
+	clusterCache clustercache.ClusterCache
 }
 
-func newStatSummaryScraper(client nodestats.StatSummaryClient) Scraper {
+func newStatSummaryScraper(client nodestats.StatSummaryClient, clusterCache clustercache.ClusterCache) Scraper {
 	return &StatSummaryScraper{
-		client: client,
+		client:       client,
+		clusterCache: clusterCache,
 	}
 }
 
 func (s *StatSummaryScraper) Scrape() []metric.Update {
+
+	nodeNameToUID := buildNodeIndex(s.clusterCache.GetAllNodes())
+	pvcNameToUID := buildPVCIndex(s.clusterCache.GetAllPersistentVolumeClaims())
+
 	var scrapeResults []metric.Update
 	nodeStats, err := s.client.GetNodeData()
 
@@ -41,11 +48,14 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 
 	for _, stat := range nodeStats {
 		nodeName := stat.Node.NodeName
+		nodeUID := string(nodeNameToUID[nodeName])
+
 		if stat.Node.CPU != nil && stat.Node.CPU.UsageCoreNanoSeconds != nil {
 			scrapeResults = append(scrapeResults, metric.Update{
 				Name: metric.NodeCPUSecondsTotal,
 				Labels: map[string]string{
 					source.KubernetesNodeLabel: nodeName,
+					source.UIDLabel:            nodeUID,
 					source.ModeLabel:           "", // TODO
 				},
 				Value: float64(*stat.Node.CPU.UsageCoreNanoSeconds) * 1e-9,
@@ -57,6 +67,7 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 				Name: metric.NodeFSCapacityBytes,
 				Labels: map[string]string{
 					source.InstanceLabel: nodeName,
+					source.UIDLabel:      nodeUID,
 					source.DeviceLabel:   "local", // This value has to be populated but isn't important here
 				},
 				Value: float64(*stat.Node.Fs.CapacityBytes),
@@ -71,6 +82,7 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 			if pod.Network != nil {
 				networkLabels := map[string]string{
 					source.UIDLabel:       podUID,
+					source.NodeUIDLabel:   nodeUID,
 					source.PodLabel:       podName,
 					source.NamespaceLabel: namespace,
 				}
@@ -93,12 +105,15 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 				if _, ok := seenPVC[*volumeStats.PVCRef]; ok {
 					continue
 				}
+				pvcUID := string(pvcNameToUID[pvcKey{name: volumeStats.PVCRef.Name, namespace: volumeStats.PVCRef.Namespace}])
 				scrapeResults = append(scrapeResults, metric.Update{
 					Name: metric.KubeletVolumeStatsUsedBytes,
 					Labels: map[string]string{
 						source.PVCLabel:       volumeStats.PVCRef.Name,
 						source.NamespaceLabel: volumeStats.PVCRef.Namespace,
 						source.UIDLabel:       podUID,
+						source.NodeUIDLabel:   nodeUID,
+						source.PVCUIDLabel:    pvcUID,
 					},
 					Value: float64(*volumeStats.UsedBytes),
 				})
@@ -116,6 +131,7 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 							source.NodeLabel:      nodeName,
 							source.InstanceLabel:  nodeName,
 							source.UIDLabel:       podUID,
+							source.NodeUIDLabel:   nodeUID,
 						},
 						Value: float64(*container.CPU.UsageCoreNanoSeconds) * 1e-9,
 					})
@@ -130,6 +146,7 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 							source.NodeLabel:      nodeName,
 							source.InstanceLabel:  nodeName,
 							source.UIDLabel:       podUID,
+							source.NodeUIDLabel:   nodeUID,
 						},
 						Value: float64(*container.Memory.WorkingSetBytes),
 					})
@@ -142,6 +159,7 @@ func (s *StatSummaryScraper) Scrape() []metric.Update {
 							source.InstanceLabel:  nodeName,
 							source.DeviceLabel:    "local",
 							source.UIDLabel:       podUID,
+							source.NodeUIDLabel:   nodeUID,
 							source.ContainerLabel: container.Name,
 						},
 						Value: float64(*container.Rootfs.UsedBytes),

+ 45 - 4
modules/collector-source/pkg/scrape/statsummary_test.go

@@ -6,10 +6,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/opencost/opencost/core/pkg/clustercache"
 	"github.com/opencost/opencost/core/pkg/source"
 	"github.com/opencost/opencost/modules/collector-source/pkg/metric"
 	"github.com/opencost/opencost/modules/collector-source/pkg/util"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
 	stats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
 )
 
@@ -24,10 +26,21 @@ func (m *mockStatSummaryClient) GetNodeData() ([]*stats.Summary, error) {
 
 func TestStatScraper_Scrape(t *testing.T) {
 	start1, _ := time.Parse(time.RFC3339, Start1Str)
+
+	testCache := &clustercache.MockClusterCache{
+		Nodes: []*clustercache.Node{
+			{Name: "node1", UID: types.UID("node-uid-1")},
+		},
+		PersistentVolumeClaims: []*clustercache.PersistentVolumeClaim{
+			{Name: "pvc1", Namespace: "namespace1", UID: types.UID("pvc-uid-1")},
+		},
+	}
+
 	tests := map[string]struct {
-		summaries []*stats.Summary
-		err       error
-		expected  []metric.Update
+		summaries    []*stats.Summary
+		err          error
+		clusterCache *clustercache.MockClusterCache
+		expected     []metric.Update
 	}{
 		"nil values": {
 			summaries: []*stats.Summary{
@@ -125,6 +138,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 			expected: []metric.Update{},
 		},
 		"single node": {
+			clusterCache: testCache,
 			summaries: []*stats.Summary{
 				{
 					Node: stats.NodeStats{
@@ -198,6 +212,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.NodeCPUSecondsTotal,
 					Labels: map[string]string{
 						source.KubernetesNodeLabel: "node1",
+						source.UIDLabel:            "node-uid-1",
 						source.ModeLabel:           "",
 					},
 					Value: 2,
@@ -206,6 +221,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.NodeFSCapacityBytes,
 					Labels: map[string]string{
 						source.InstanceLabel: "node1",
+						source.UIDLabel:      "node-uid-1",
 						source.DeviceLabel:   "local",
 					},
 					Value: float64(2 * util.GB),
@@ -214,6 +230,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.ContainerNetworkReceiveBytesTotal,
 					Labels: map[string]string{
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.PodLabel:       "pod1",
 						source.NamespaceLabel: "namespace1",
 					},
@@ -223,6 +240,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.ContainerNetworkTransmitBytesTotal,
 					Labels: map[string]string{
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.PodLabel:       "pod1",
 						source.NamespaceLabel: "namespace1",
 					},
@@ -234,6 +252,8 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.PVCLabel:       "pvc1",
 						source.NamespaceLabel: "namespace1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
+						source.PVCUIDLabel:    "pvc-uid-1",
 					},
 					Value: float64(1 * util.GB),
 				},
@@ -246,6 +266,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.NodeLabel:      "node1",
 						source.InstanceLabel:  "node1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 					},
 					Value: 1,
 				},
@@ -258,6 +279,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.NodeLabel:      "node1",
 						source.InstanceLabel:  "node1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 					},
 					Value: float64(5 * util.MB),
 				},
@@ -267,6 +289,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.InstanceLabel:  "node1",
 						source.DeviceLabel:    "local",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.ContainerLabel: "container1",
 					},
 					Value: float64(1 * util.GB),
@@ -274,6 +297,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 			},
 		},
 		"single node with error": {
+			clusterCache: testCache,
 			summaries: []*stats.Summary{
 				{
 					Node: stats.NodeStats{
@@ -348,6 +372,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.NodeCPUSecondsTotal,
 					Labels: map[string]string{
 						source.KubernetesNodeLabel: "node1",
+						source.UIDLabel:            "node-uid-1",
 						source.ModeLabel:           "",
 					},
 					Value: 2,
@@ -356,6 +381,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.NodeFSCapacityBytes,
 					Labels: map[string]string{
 						source.InstanceLabel: "node1",
+						source.UIDLabel:      "node-uid-1",
 						source.DeviceLabel:   "local",
 					},
 					Value: float64(2 * util.GB),
@@ -364,6 +390,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.ContainerNetworkReceiveBytesTotal,
 					Labels: map[string]string{
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.PodLabel:       "pod1",
 						source.NamespaceLabel: "namespace1",
 					},
@@ -373,6 +400,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 					Name: metric.ContainerNetworkTransmitBytesTotal,
 					Labels: map[string]string{
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.PodLabel:       "pod1",
 						source.NamespaceLabel: "namespace1",
 					},
@@ -384,6 +412,8 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.PVCLabel:       "pvc1",
 						source.NamespaceLabel: "namespace1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
+						source.PVCUIDLabel:    "pvc-uid-1",
 					},
 					Value: float64(1 * util.GB),
 				},
@@ -396,6 +426,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.NodeLabel:      "node1",
 						source.InstanceLabel:  "node1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 					},
 					Value: 1,
 				},
@@ -408,6 +439,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.NodeLabel:      "node1",
 						source.InstanceLabel:  "node1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 					},
 					Value: float64(5 * util.MB),
 				},
@@ -417,6 +449,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.InstanceLabel:  "node1",
 						source.DeviceLabel:    "local",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
 						source.ContainerLabel: "container1",
 					},
 					Value: float64(1 * util.GB),
@@ -424,6 +457,7 @@ func TestStatScraper_Scrape(t *testing.T) {
 			},
 		},
 		"repeat pvc": {
+			clusterCache: testCache,
 			summaries: []*stats.Summary{
 				{
 					Node: stats.NodeStats{
@@ -480,6 +514,8 @@ func TestStatScraper_Scrape(t *testing.T) {
 						source.PVCLabel:       "pvc1",
 						source.NamespaceLabel: "namespace1",
 						source.UIDLabel:       "uid1",
+						source.NodeUIDLabel:   "node-uid-1",
+						source.PVCUIDLabel:    "pvc-uid-1",
 					},
 					Value: float64(1 * util.GB),
 				},
@@ -488,8 +524,13 @@ func TestStatScraper_Scrape(t *testing.T) {
 	}
 	for name, tt := range tests {
 		t.Run(name, func(t *testing.T) {
+			cache := clustercache.ClusterCache(tt.clusterCache)
+			if tt.clusterCache == nil {
+				cache = &clustercache.MockClusterCache{}
+			}
 			s := &StatSummaryScraper{
-				client: &mockStatSummaryClient{results: tt.summaries},
+				client:       &mockStatSummaryClient{results: tt.summaries, err: tt.err},
+				clusterCache: cache,
 			}
 			scrapeResults := s.Scrape()
 

+ 1 - 72
pkg/cloud/aws/provider_test.go

@@ -696,77 +696,6 @@ func Test_configUpdaterWithReaderAndType_forSpotValues(t *testing.T) {
 	}
 }
 
-// Mock cluster cache for testing
-type mockClusterCache struct {
-	pods []*clustercache.Pod
-}
-
-func (m *mockClusterCache) Run()  {}
-func (m *mockClusterCache) Stop() {}
-
-func (m *mockClusterCache) GetAllPods() []*clustercache.Pod {
-	return m.pods
-}
-
-func (m *mockClusterCache) GetAllNodes() []*clustercache.Node {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllPersistentVolumes() []*clustercache.PersistentVolume {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllPersistentVolumeClaims() []*clustercache.PersistentVolumeClaim {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllStorageClasses() []*clustercache.StorageClass {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllServices() []*clustercache.Service {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllDeployments() []*clustercache.Deployment {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllDaemonSets() []*clustercache.DaemonSet {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllStatefulSets() []*clustercache.StatefulSet {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllReplicaSets() []*clustercache.ReplicaSet {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllJobs() []*clustercache.Job {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllCronJobs() []*clustercache.CronJob {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllNamespaces() []*clustercache.Namespace {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllPodDisruptionBudgets() []*clustercache.PodDisruptionBudget {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllReplicationControllers() []*clustercache.ReplicationController {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllResourceQuotas() []*clustercache.ResourceQuota {
-	return nil
-}
 
 func TestAWS_getFargatePod(t *testing.T) {
 	tests := []struct {
@@ -827,7 +756,7 @@ func TestAWS_getFargatePod(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			aws := &AWS{
-				Clientset: &mockClusterCache{pods: tt.pods},
+				Clientset: &clustercache.MockClusterCache{Pods: tt.pods},
 			}
 
 			gotPod, gotBool := aws.getFargatePod(tt.awsKey)

+ 7 - 49
pkg/cloud/gcp/provider_test.go

@@ -454,7 +454,7 @@ func TestGCP_GetManagementPlatform(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			gcp := &GCP{
-				Clientset: &mockClusterCache{nodes: tt.nodes},
+				Clientset: &clustercache.MockClusterCache{Nodes: tt.nodes},
 			}
 
 			result, err := gcp.GetManagementPlatform()
@@ -1045,10 +1045,10 @@ func TestGCP_parsePages(t *testing.T) {
 func TestGCP_DownloadPricingData(t *testing.T) {
 	gcp := &GCP{
 		Config: &mockConfig{},
-		Clientset: &mockClusterCache{
-			nodes: []*clustercache.Node{},
-			pvs:   []*clustercache.PersistentVolume{},
-			scs:   []*clustercache.StorageClass{},
+		Clientset: &clustercache.MockClusterCache{
+			Nodes:             []*clustercache.Node{},
+			PersistentVolumes: []*clustercache.PersistentVolume{},
+			StorageClasses:    []*clustercache.StorageClass{},
 		},
 	}
 
@@ -1100,8 +1100,8 @@ func TestGCP_ApplyReservedInstancePricing(t *testing.T) {
 				},
 			},
 		},
-		Clientset: &mockClusterCache{
-			nodes: []*clustercache.Node{
+		Clientset: &clustercache.MockClusterCache{
+			Nodes: []*clustercache.Node{
 				{
 					Name: "test-node",
 					Labels: map[string]string{
@@ -1280,48 +1280,6 @@ func (m *mockConfig) ConfigFileManager() *config.ConfigFileManager {
 	return nil
 }
 
-type mockClusterCache struct {
-	nodes []*clustercache.Node
-	pvs   []*clustercache.PersistentVolume
-	scs   []*clustercache.StorageClass
-}
-
-func (m *mockClusterCache) GetAllNodes() []*clustercache.Node {
-	return m.nodes
-}
-
-func (m *mockClusterCache) GetAllDaemonSets() []*clustercache.DaemonSet {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllDeployments() []*clustercache.Deployment {
-	return nil
-}
-
-func (m *mockClusterCache) Run()                                                      {}
-func (m *mockClusterCache) Stop()                                                     {}
-func (m *mockClusterCache) GetAllNamespaces() []*clustercache.Namespace               { return nil }
-func (m *mockClusterCache) GetAllPods() []*clustercache.Pod                           { return nil }
-func (m *mockClusterCache) GetAllServices() []*clustercache.Service                   { return nil }
-func (m *mockClusterCache) GetAllStatefulSets() []*clustercache.StatefulSet           { return nil }
-func (m *mockClusterCache) GetAllReplicaSets() []*clustercache.ReplicaSet             { return nil }
-func (m *mockClusterCache) GetAllPersistentVolumes() []*clustercache.PersistentVolume { return m.pvs }
-func (m *mockClusterCache) GetAllPersistentVolumeClaims() []*clustercache.PersistentVolumeClaim {
-	return nil
-}
-func (m *mockClusterCache) GetAllStorageClasses() []*clustercache.StorageClass { return m.scs }
-func (m *mockClusterCache) GetAllJobs() []*clustercache.Job                    { return nil }
-func (m *mockClusterCache) GetAllCronJobs() []*clustercache.CronJob            { return nil }
-func (m *mockClusterCache) GetAllPodDisruptionBudgets() []*clustercache.PodDisruptionBudget {
-	return nil
-}
-func (m *mockClusterCache) GetAllReplicationControllers() []*clustercache.ReplicationController {
-	return nil
-}
-
-func (m *mockClusterCache) GetAllResourceQuotas() []*clustercache.ResourceQuota {
-	return nil
-}
 
 type mockMetadataClient struct{}
 

+ 4 - 23
pkg/cloud/provider/cloud_test.go

@@ -279,7 +279,7 @@ func TestNodePriceFromCSVWithGPULabels(t *testing.T) {
 
 	c.DownloadPricingData()
 
-	fc := NewFakeNodeCache([]*clustercache.Node{n})
+	fc := &clustercache.MockClusterCache{Nodes: []*clustercache.Node{n}}
 	fm := FakeClusterMap{}
 	d, _ := time.ParseDuration("1m")
 
@@ -347,7 +347,7 @@ func TestRKE2NodePriceFromCSVWithGPULabels(t *testing.T) {
 
 	c.DownloadPricingData()
 
-	fc := NewFakeNodeCache([]*clustercache.Node{n})
+	fc := &clustercache.MockClusterCache{Nodes: []*clustercache.Node{n}}
 	fm := FakeClusterMap{}
 	d, _ := time.ParseDuration("1m")
 
@@ -560,25 +560,6 @@ func TestNodePriceFromCSVWithRegion(t *testing.T) {
 	}
 }
 
-type FakeCache struct {
-	nodes []*clustercache.Node
-	clustercache.ClusterCache
-}
-
-func (f FakeCache) GetAllNodes() []*clustercache.Node {
-	return f.nodes
-}
-
-func (f FakeCache) GetAllDaemonSets() []*clustercache.DaemonSet {
-	return nil
-}
-
-func NewFakeNodeCache(nodes []*clustercache.Node) FakeCache {
-	return FakeCache{
-		nodes: nodes,
-	}
-}
-
 type FakeClusterMap struct {
 	clusters.ClusterMap
 }
@@ -664,7 +645,7 @@ func TestNodePriceFromCSVWithBadConfig(t *testing.T) {
 	n.Labels["foo"] = "labelFooWant"
 	n.Labels[v1.LabelTopologyRegion] = "regionone"
 
-	fc := NewFakeNodeCache([]*clustercache.Node{n})
+	fc := &clustercache.MockClusterCache{Nodes: []*clustercache.Node{n}}
 	fm := FakeClusterMap{}
 	d, _ := time.ParseDuration("1m")
 
@@ -721,7 +702,7 @@ func TestSourceMatchesFromCSV(t *testing.T) {
 	n3.Labels[v1.LabelTopologyRegion] = "eastus2"
 	n3.Labels[v1.LabelInstanceTypeStable] = "Standard_F32s_v2"
 
-	fc := NewFakeNodeCache([]*clustercache.Node{n, n2, n3})
+	fc := &clustercache.MockClusterCache{Nodes: []*clustercache.Node{n, n2, n3}}
 	fm := FakeClusterMap{}
 	d, _ := time.ParseDuration("1m")
 

+ 2 - 17
pkg/costmodel/costmodel_test.go

@@ -470,7 +470,7 @@ func TestNodeCostAnnotations(t *testing.T) {
 
 	costModel := &CostModel{
 		Provider: customProvider,
-		Cache: NewFakeNodeCache([]*clustercache.Node{
+		Cache: &clustercache.MockClusterCache{Nodes: []*clustercache.Node{
 			{
 				Name: "test-node-001",
 				Labels: map[string]string{
@@ -487,7 +487,7 @@ func TestNodeCostAnnotations(t *testing.T) {
 					"opencost.io/node-ram-hourly-cost": "222",
 				},
 			},
-		}),
+		}},
 	}
 	assert.NotNil(t, costModel)
 
@@ -531,18 +531,3 @@ func TestNodeCostAnnotations(t *testing.T) {
 	}
 }
 
-// FakeNodeCache implements ClusterCache interface for testing
-type FakeNodeCache struct {
-	clustercache.ClusterCache
-	nodes []*clustercache.Node
-}
-
-func (f FakeNodeCache) GetAllNodes() []*clustercache.Node {
-	return f.nodes
-}
-
-func NewFakeNodeCache(nodes []*clustercache.Node) FakeNodeCache {
-	return FakeNodeCache{
-		nodes: nodes,
-	}
-}

+ 27 - 39
pkg/kubemodel/kubemodel.go

@@ -201,10 +201,10 @@ func (km *KubeModel) computeNodes(kms *kubemodel.KubeModelSet, start, end time.T
 	nodeIsSpotResultFuture := source.WithGroup(grp, metrics.QueryNodeIsSpot(start, end))
 	nodeResourceCapacitiesFuture := source.WithGroup(grp, metrics.QueryNodeResourceCapacities(start, end))
 	nodeResourcesAllocatableFuture := source.WithGroup(grp, metrics.QueryNodeResourcesAllocatable(start, end))
-	// TODO before merge add Node UID to Nodestates metrics
-	//localStorageBytesFuture := source.WithGroup(grp, metrics.QueryLocalStorageBytes(start, end))
-	//localStorageUsedAvgFuture := source.WithGroup(grp, metrics.QueryLocalStorageUsedAvg(start, end))
-	//localStorageUsedMaxFuture := source.WithGroup(grp, metrics.QueryLocalStorageUsedMax(start, end))
+
+	localStorageBytesFuture := source.WithGroup(grp, metrics.QueryLocalStorageBytes(start, end))
+	localStorageUsedAvgFuture := source.WithGroup(grp, metrics.QueryLocalStorageUsedAvg(start, end))
+	localStorageUsedMaxFuture := source.WithGroup(grp, metrics.QueryLocalStorageUsedMax(start, end))
 
 	nodeMap := make(map[string]*kubemodel.Node)
 
@@ -276,41 +276,29 @@ func (km *KubeModel) computeNodes(kms *kubemodel.KubeModelSet, start, end time.T
 		node.Labels = res.Labels
 	}
 
-	//localStorageBytesResult, _ := localStorageBytesFuture.Await()
-	//for _, res := range localStorageBytesResult {
-	//	uid, ok := nodeNameToUID[res.Instance]
-	//	if !ok {
-	//		continue
-	//	}
-	//	node := nodeMap[uid]
-	//	if len(res.Data) > 0 {
-	//		node.FileSystem.CapacityBytes = res.Data[0].Value
-	//	}
-	//}
-	//
-	//localStorageUsedAvgResult, _ := localStorageUsedAvgFuture.Await()
-	//for _, res := range localStorageUsedAvgResult {
-	//	uid, ok := nodeNameToUID[res.Instance]
-	//	if !ok {
-	//		continue
-	//	}
-	//	node := nodeMap[uid]
-	//	if len(res.Data) > 0 {
-	//		node.FileSystem.UsageByteAvg = res.Data[0].Value
-	//	}
-	//}
-	//
-	//localStorageUsedMaxResult, _ := localStorageUsedMaxFuture.Await()
-	//for _, res := range localStorageUsedMaxResult {
-	//	uid, ok := nodeNameToUID[res.Instance]
-	//	if !ok {
-	//		continue
-	//	}
-	//	node := nodeMap[uid]
-	//	if len(res.Data) > 0 {
-	//		node.FileSystem.UsageByteMax = res.Data[0].Value
-	//	}
-	//}
+	localStorageBytesResult, _ := localStorageBytesFuture.Await()
+	for _, res := range localStorageBytesResult {
+		node, ok := nodeMap[res.UID]
+		if ok && len(res.Data) > 0 {
+			node.FileSystem.CapacityBytes = res.Data[0].Value
+		}
+	}
+
+	localStorageUsedAvgResult, _ := localStorageUsedAvgFuture.Await()
+	for _, res := range localStorageUsedAvgResult {
+		node, ok := nodeMap[res.UID]
+		if ok && len(res.Data) > 0 {
+			node.FileSystem.UsageByteAvg = res.Data[0].Value
+		}
+	}
+
+	localStorageUsedMaxResult, _ := localStorageUsedMaxFuture.Await()
+	for _, res := range localStorageUsedMaxResult {
+		node, ok := nodeMap[res.UID]
+		if ok && len(res.Data) > 0 {
+			node.FileSystem.UsageByteMax = res.Data[0].Value
+		}
+	}
 
 	for _, node := range nodeMap {
 		err := kms.RegisterNode(node)

+ 5 - 20
pkg/metrics/deploymentmetrics_test.go

@@ -33,7 +33,7 @@ func TestKubecostDeploymentCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kdc := KubecostDeploymentCollector{
-				KubeClusterCache: NewFakeDeploymentCache([]*clustercache.Deployment{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -148,7 +148,7 @@ func TestKubecostDeploymentCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kdc := KubecostDeploymentCollector{
-				KubeClusterCache: NewFakeDeploymentCache(tt.deployments),
+				KubeClusterCache: &clustercache.MockClusterCache{Deployments: tt.deployments},
 				metricsConfig:    mc,
 			}
 
@@ -254,7 +254,7 @@ func TestKubeDeploymentCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kdc := KubeDeploymentCollector{
-				KubeClusterCache: NewFakeDeploymentCache([]*clustercache.Deployment{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -383,7 +383,7 @@ func TestKubeDeploymentCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kdc := KubeDeploymentCollector{
-				KubeClusterCache: NewFakeDeploymentCache(tt.deployments),
+				KubeClusterCache: &clustercache.MockClusterCache{Deployments: tt.deployments},
 				metricsConfig:    mc,
 			}
 
@@ -507,7 +507,7 @@ func TestKubeDeploymentCollector_DefaultReplicas(t *testing.T) {
 		DisabledMetrics: []string{"kube_deployment_status_replicas_available"}, // Only test spec replicas
 	}
 	kdc := KubeDeploymentCollector{
-		KubeClusterCache: NewFakeDeploymentCache([]*clustercache.Deployment{deployment}),
+		KubeClusterCache: &clustercache.MockClusterCache{Deployments: []*clustercache.Deployment{deployment}},
 		metricsConfig:    mc,
 	}
 
@@ -524,18 +524,3 @@ func TestKubeDeploymentCollector_DefaultReplicas(t *testing.T) {
 	}
 }
 
-// FakeDeploymentCache implements ClusterCache interface for testing
-type FakeDeploymentCache struct {
-	clustercache.ClusterCache
-	deployments []*clustercache.Deployment
-}
-
-func (f FakeDeploymentCache) GetAllDeployments() []*clustercache.Deployment {
-	return f.deployments
-}
-
-func NewFakeDeploymentCache(deployments []*clustercache.Deployment) FakeDeploymentCache {
-	return FakeDeploymentCache{
-		deployments: deployments,
-	}
-}

+ 2 - 11
pkg/metrics/jobmetrics_test.go

@@ -10,19 +10,10 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 )
 
-type mockJobCache struct {
-	clustercache.ClusterCache
-	jobs []*clustercache.Job
-}
-
-func (m mockJobCache) GetAllJobs() []*clustercache.Job {
-	return m.jobs
-}
-
 func TestKubeJobCollector_Collect(t *testing.T) {
 	// Test with job that has no failures
-	cache := mockJobCache{
-		jobs: []*clustercache.Job{
+	cache := &clustercache.MockClusterCache{
+		Jobs: []*clustercache.Job{
 			{
 				Name:      "test-job",
 				Namespace: "default",

+ 4 - 13
pkg/metrics/namespacemetrics_test.go

@@ -9,19 +9,10 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 )
 
-type mockNamespaceCache struct {
-	clustercache.ClusterCache
-	namespaces []*clustercache.Namespace
-}
-
-func (m mockNamespaceCache) GetAllNamespaces() []*clustercache.Namespace {
-	return m.namespaces
-}
-
 func TestKubecostNamespaceCollector_Collect(t *testing.T) {
 	// Test with namespace that has annotations
-	cache := mockNamespaceCache{
-		namespaces: []*clustercache.Namespace{
+	cache := &clustercache.MockClusterCache{
+		Namespaces: []*clustercache.Namespace{
 			{
 				Name:        "test-ns",
 				UID:         types.UID("test-uid"),
@@ -53,8 +44,8 @@ func TestKubecostNamespaceCollector_Collect(t *testing.T) {
 
 func TestKubeNamespaceCollector_Collect(t *testing.T) {
 	// Test with namespace that has labels
-	cache := mockNamespaceCache{
-		namespaces: []*clustercache.Namespace{
+	cache := &clustercache.MockClusterCache{
+		Namespaces: []*clustercache.Namespace{
 			{
 				Name:   "test-ns",
 				UID:    types.UID("test-uid"),

+ 2 - 17
pkg/metrics/nodemetrics_test.go

@@ -40,7 +40,7 @@ func TestKubeNodeCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			nc := KubeNodeCollector{
-				KubeClusterCache: NewFakeNodeCache([]*clustercache.Node{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -166,7 +166,7 @@ func TestKubeNodeCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			nc := KubeNodeCollector{
-				KubeClusterCache: NewFakeNodeCache(tt.nodes),
+				KubeClusterCache: &clustercache.MockClusterCache{Nodes: tt.nodes},
 				metricsConfig:    mc,
 			}
 
@@ -437,18 +437,3 @@ func TestGetConditions(t *testing.T) {
 	}
 }
 
-// FakeNodeCache implements ClusterCache interface for testing
-type FakeNodeCache struct {
-	clustercache.ClusterCache
-	nodes []*clustercache.Node
-}
-
-func (f FakeNodeCache) GetAllNodes() []*clustercache.Node {
-	return f.nodes
-}
-
-func NewFakeNodeCache(nodes []*clustercache.Node) FakeNodeCache {
-	return FakeNodeCache{
-		nodes: nodes,
-	}
-}

+ 1 - 27
pkg/metrics/podlabelmetrics_test.go

@@ -20,7 +20,7 @@ func TestWhitelist(t *testing.T) {
 
 	sampleStatefulSets := []*clustercache.StatefulSet{}
 
-	kc := NewFakeCache(sampleReplicaSets, sampleStatefulSets, sampleServices)
+	kc := &clustercache.MockClusterCache{ReplicaSets: sampleReplicaSets, StatefulSets: sampleStatefulSets, Services: sampleServices}
 	wl := map[string]bool{
 		"whitelistedlabel": true,
 	}
@@ -43,29 +43,3 @@ func TestWhitelist(t *testing.T) {
 
 }
 
-type FakeCache struct {
-	clustercache.ClusterCache
-	replicasets  []*clustercache.ReplicaSet
-	statefulsets []*clustercache.StatefulSet
-	services     []*clustercache.Service
-}
-
-func (f FakeCache) GetAllReplicaSets() []*clustercache.ReplicaSet {
-	return f.replicasets
-}
-
-func (f FakeCache) GetAllStatefulSets() []*clustercache.StatefulSet {
-	return f.statefulsets
-}
-
-func (f FakeCache) GetAllServices() []*clustercache.Service {
-	return f.services
-}
-
-func NewFakeCache(replicasets []*clustercache.ReplicaSet, statefulsets []*clustercache.StatefulSet, services []*clustercache.Service) FakeCache {
-	return FakeCache{
-		replicasets:  replicasets,
-		statefulsets: statefulsets,
-		services:     services,
-	}
-}

+ 5 - 20
pkg/metrics/podmetrics_test.go

@@ -36,7 +36,7 @@ func TestKubecostPodCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kpc := KubecostPodCollector{
-				KubeClusterCache: NewFakePodCache([]*clustercache.Pod{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -135,7 +135,7 @@ func TestKubecostPodCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kpc := KubecostPodCollector{
-				KubeClusterCache: NewFakePodCache(tt.pods),
+				KubeClusterCache: &clustercache.MockClusterCache{Pods: tt.pods},
 				metricsConfig:    mc,
 			}
 
@@ -249,7 +249,7 @@ func TestKubePodCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kpc := KubePodCollector{
-				KubeClusterCache: NewFakePodCache([]*clustercache.Pod{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -485,7 +485,7 @@ func TestKubePodCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			kpc := KubePodCollector{
-				KubeClusterCache: NewFakePodCache(tt.pods),
+				KubeClusterCache: &clustercache.MockClusterCache{Pods: tt.pods},
 				metricsConfig:    mc,
 			}
 
@@ -808,7 +808,7 @@ func TestPodPhaseMetrics(t *testing.T) {
 		DisabledMetrics: []string{"kube_pod_labels"}, // Only test phase metrics
 	}
 	kpc := KubePodCollector{
-		KubeClusterCache: NewFakePodCache([]*clustercache.Pod{pod}),
+		KubeClusterCache: &clustercache.MockClusterCache{Pods: []*clustercache.Pod{pod}},
 		metricsConfig:    mc,
 	}
 
@@ -846,18 +846,3 @@ func TestPodPhaseMetrics(t *testing.T) {
 	}
 }
 
-// FakePodCache implements ClusterCache interface for testing
-type FakePodCache struct {
-	clustercache.ClusterCache
-	pods []*clustercache.Pod
-}
-
-func (f FakePodCache) GetAllPods() []*clustercache.Pod {
-	return f.pods
-}
-
-func NewFakePodCache(pods []*clustercache.Pod) FakePodCache {
-	return FakePodCache{
-		pods: pods,
-	}
-}

+ 1 - 15
pkg/metrics/pvcmetrics_test.go

@@ -56,7 +56,7 @@ func TestKubePVCCollector_Collect(t *testing.T) {
 		},
 	}
 
-	cache := NewFakePVCCache([]*clustercache.PersistentVolumeClaim{pvc})
+	cache := &clustercache.MockClusterCache{PersistentVolumeClaims: []*clustercache.PersistentVolumeClaim{pvc}}
 	collector := KubePVCCollector{
 		KubeClusterCache: cache,
 		metricsConfig:    MetricsConfig{},
@@ -106,17 +106,3 @@ func TestKubePVCMetrics_UIDLabel(t *testing.T) {
 	t.Error("UID label not found in metric")
 }
 
-type FakePVCCache struct {
-	clustercache.ClusterCache
-	pvcs []*clustercache.PersistentVolumeClaim
-}
-
-func (f FakePVCCache) GetAllPersistentVolumeClaims() []*clustercache.PersistentVolumeClaim {
-	return f.pvcs
-}
-
-func NewFakePVCCache(pvcs []*clustercache.PersistentVolumeClaim) FakePVCCache {
-	return FakePVCCache{
-		pvcs: pvcs,
-	}
-}

+ 1 - 15
pkg/metrics/pvmetrics_test.go

@@ -58,7 +58,7 @@ func TestKubePVCollector_Collect(t *testing.T) {
 		},
 	}
 
-	cache := NewFakePVCache([]*clustercache.PersistentVolume{pv})
+	cache := &clustercache.MockClusterCache{PersistentVolumes: []*clustercache.PersistentVolume{pv}}
 	collector := KubePVCollector{
 		KubeClusterCache: cache,
 		metricsConfig:    MetricsConfig{},
@@ -108,17 +108,3 @@ func TestKubePVMetrics_UIDLabel(t *testing.T) {
 	t.Error("UID label not found in metric")
 }
 
-type FakePVCache struct {
-	clustercache.ClusterCache
-	pvs []*clustercache.PersistentVolume
-}
-
-func (f FakePVCache) GetAllPersistentVolumes() []*clustercache.PersistentVolume {
-	return f.pvs
-}
-
-func NewFakePVCache(pvs []*clustercache.PersistentVolume) FakePVCache {
-	return FakePVCache{
-		pvs: pvs,
-	}
-}

+ 2 - 17
pkg/metrics/servicemetrics_test.go

@@ -33,7 +33,7 @@ func TestKubecostServiceCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			sc := KubecostServiceCollector{
-				KubeClusterCache: NewFakeServiceCache([]*clustercache.Service{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -129,7 +129,7 @@ func TestKubecostServiceCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			sc := KubecostServiceCollector{
-				KubeClusterCache: NewFakeServiceCache(tt.services),
+				KubeClusterCache: &clustercache.MockClusterCache{Services: tt.services},
 				metricsConfig:    mc,
 			}
 
@@ -216,18 +216,3 @@ func TestServiceSelectorLabelsMetric_EmptyLabels(t *testing.T) {
 	}
 }
 
-// FakeServiceCache implements ClusterCache interface for testing
-type FakeServiceCache struct {
-	clustercache.ClusterCache
-	services []*clustercache.Service
-}
-
-func (f FakeServiceCache) GetAllServices() []*clustercache.Service {
-	return f.services
-}
-
-func NewFakeServiceCache(services []*clustercache.Service) FakeServiceCache {
-	return FakeServiceCache{
-		services: services,
-	}
-}

+ 2 - 17
pkg/metrics/statefulsetmetrics_test.go

@@ -34,7 +34,7 @@ func TestKubecostStatefulsetCollector_Describe(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			sc := KubecostStatefulsetCollector{
-				KubeClusterCache: NewFakeStatefulsetCache([]*clustercache.StatefulSet{}),
+				KubeClusterCache: &clustercache.MockClusterCache{},
 				metricsConfig:    mc,
 			}
 
@@ -153,7 +153,7 @@ func TestKubecostStatefulsetCollector_Collect(t *testing.T) {
 				DisabledMetrics: tt.disabledMetrics,
 			}
 			sc := KubecostStatefulsetCollector{
-				KubeClusterCache: NewFakeStatefulsetCache(tt.statefulsets),
+				KubeClusterCache: &clustercache.MockClusterCache{StatefulSets: tt.statefulsets},
 				metricsConfig:    mc,
 			}
 
@@ -285,18 +285,3 @@ func TestStatefulsetMatchLabelsMetric_MissingFields(t *testing.T) {
 	}
 }
 
-// FakeStatefulsetCache implements ClusterCache interface for testing
-type FakeStatefulsetCache struct {
-	clustercache.ClusterCache
-	statefulsets []*clustercache.StatefulSet
-}
-
-func (f FakeStatefulsetCache) GetAllStatefulSets() []*clustercache.StatefulSet {
-	return f.statefulsets
-}
-
-func NewFakeStatefulsetCache(statefulsets []*clustercache.StatefulSet) FakeStatefulsetCache {
-	return FakeStatefulsetCache{
-		statefulsets: statefulsets,
-	}
-}