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

Move mock generation to mock.go and clean up old code

Sean Holcomb 5 лет назад
Родитель
Сommit
d2d9978f19

+ 1 - 131
pkg/kubecost/allocation.go

@@ -1651,6 +1651,7 @@ func (as *AllocationSet) ComputeIdleAllocations(assetSet *AssetSet) (map[string]
 // allocation with idle reflects the adjusted node costs. One idle allocation
 // per-node will be computed and returned, keyed by cluster_id.
 func (as *AllocationSet) ComputeIdleAllocationsByNode(assetSet *AssetSet) (map[string]*Allocation, error) {
+
 	if as == nil {
 		return nil, fmt.Errorf("cannot compute idle allocation for nil AllocationSet")
 	}
@@ -1784,137 +1785,6 @@ func (as *AllocationSet) ComputeIdleAllocationsByNode(assetSet *AssetSet) (map[s
 	return idleAllocs, nil
 }
 
-// Reconcile calculate the exact cost of Allocation by resource(cpu, ram, gpu etc) based on Asset(s) on which
-// the Allocation depends.
-func (as *AllocationSet) Reconcile(assetSet *AssetSet) error {
-	if as == nil {
-		return fmt.Errorf("cannot reconcile allocation for nil AllocationSet")
-	}
-
-	if assetSet == nil {
-		return fmt.Errorf("cannot reconcile allocation with nil AssetSet")
-	}
-
-	if !as.Window.Equal(assetSet.Window) {
-		return fmt.Errorf("cannot reconcile allocation for sets with mismatched windows: %s != %s", as.Window, assetSet.Window)
-	}
-
-	// Build map of Assets with type Node by their ProviderId so that they can be matched to Allocations to determine
-	// proper CPU GPU and RAM prices
-	nodeByProviderID := map[string]*Node{}
-	diskByName := map[string]*Disk{}
-	assetSet.Each(func(key string, a Asset) {
-		if node, ok := a.(*Node); ok && node.properties.ProviderID != "" {
-			nodeByProviderID[node.properties.ProviderID] = node
-		}
-		if disk, ok := a.(*Disk); ok {
-			diskByName[disk.properties.Name] = disk
-		}
-	})
-
-	// Match Assets against allocations and adjust allocation cost based on the proportion of the asset that they used
-	as.Each(func(name string, a *Allocation) {
-		a.reconcileNodes(nodeByProviderID)
-		a.reconcileDisks(diskByName)
-	})
-
-	return nil
-}
-
-func (a *Allocation) reconcileNodes(nodeByProviderID map[string]*Node) {
-	providerId := a.Properties.ProviderID
-
-	// Reconcile with node Assets
-	node, ok := nodeByProviderID[providerId]
-	if !ok {
-		// Failed to find node for allocation
-		return
-	}
-
-	// adjustmentRate is used to scale resource costs proportionally
-	// by the adjustment. This is necessary because we only get one
-	// adjustment per Node, not one per-resource-per-Node.
-	//
-	// e.g. total cost = $90, adjustment = -$10 => 0.9
-	// e.g. total cost = $150, adjustment = -$300 => 0.3333
-	// e.g. total cost = $150, adjustment = $50 => 1.5
-	adjustmentRate := 1.0
-	if node.TotalCost()-node.Adjustment() == 0 {
-		// If (totalCost - adjustment) is 0.0 then adjustment cancels
-		// the entire node cost and we should make everything 0
-		// without dividing by 0.
-		adjustmentRate = 0.0
-	} else if node.Adjustment() != 0.0 {
-		// adjustmentRate is the ratio of cost-with-adjustment (i.e. TotalCost)
-		// to cost-without-adjustment (i.e. TotalCost - Adjustment).
-		adjustmentRate = node.TotalCost() / (node.TotalCost() - node.Adjustment())
-	}
-
-	// Find total cost of each node resource for the window
-	cpuCost := node.CPUCost * (1.0 - node.Discount) * adjustmentRate
-	ramCost := node.RAMCost * (1.0 - node.Discount) * adjustmentRate
-	gpuCost := node.GPUCost * adjustmentRate
-
-	// Find the proportion of resource hours used by the allocation, checking for 0 denominators
-	cpuUsageProportion := 0.0
-	if node.CPUCoreHours != 0 {
-		cpuUsageProportion = a.CPUCoreHours / node.CPUCoreHours
-	} else {
-		log.Warningf("Missing CPU Hours for node Provider ID: %s", providerId)
-	}
-	ramUsageProportion := 0.0
-	if node.RAMByteHours != 0 {
-		ramUsageProportion = a.RAMByteHours / node.RAMByteHours
-	} else {
-		log.Warningf("Missing Ram Byte Hours for node Provider ID: %s", providerId)
-	}
-	gpuUsageProportion := 0.0
-	if node.GPUHours != 0 {
-		gpuUsageProportion = a.GPUHours / node.GPUHours
-	}
-	// No log for GPU because not all nodes have GPU
-
-	// Calculate the allocation's resource costs by the proportion of resources used and total costs
-	allocCPUCost := cpuUsageProportion * cpuCost
-	allocRAMCost := ramUsageProportion * ramCost
-	allocGPUCost := gpuUsageProportion * gpuCost
-
-	a.CPUCostAdjustment = allocCPUCost - a.CPUCost
-	a.RAMCostAdjustment = allocRAMCost - a.RAMCost
-	a.GPUCostAdjustment = allocGPUCost - a.GPUCost
-}
-
-func (a *Allocation) reconcileDisks(diskByName map[string]*Disk) {
-	pvs := a.PVs
-	if pvs == nil {
-		// No PV usage to reconcile
-		return
-	}
-	// Set PV Adjustment for allocation to 0 for idempotency
-	a.PVCostAdjustment = 0.0
-	for pvKey, pvUsage := range pvs {
-		disk, ok := diskByName[pvKey.Name]
-		if !ok {
-			// Failed to find disk in assets
-			continue
-		}
-		// Check the proportion of disk that is being used by
-		pvUsageProportion := 0.0
-		if disk.ByteHours != 0 {
-			pvUsageProportion = pvUsage.ByteHours / disk.ByteHours
-		} else {
-			log.Warningf("Missing Byte Hours for disk: %s", pvKey)
-		}
-
-		// take proportion of disk adjusted cost
-		allocPVCost := pvUsageProportion * disk.TotalCost()
-
-		// PVCostAdjustment is cumulative as there can be many PVs for each Allocation
-		a.PVCostAdjustment += allocPVCost - pvUsage.Cost
-	}
-
-}
-
 // Delete removes the allocation with the given name from the set
 func (as *AllocationSet) Delete(name string) {
 	if as == nil {

+ 8 - 721
pkg/kubecost/allocation_test.go

@@ -10,82 +10,6 @@ import (
 	"github.com/kubecost/cost-model/pkg/util"
 )
 
-const day = 24 * time.Hour
-
-var disk = PVKey{}
-var disk1 = PVKey{
-	Cluster: "cluster2",
-	Name:    "disk1",
-}
-var disk2 = PVKey{
-	Cluster: "cluster2",
-	Name:    "disk2",
-}
-
-func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *AllocationProperties) *Allocation {
-	if name == "" {
-		name = "cluster1/namespace1/pod1/container1"
-	}
-
-	properties := &AllocationProperties{}
-	if props == nil {
-		properties.Cluster = "cluster1"
-		properties.Node = "node1"
-		properties.Namespace = "namespace1"
-		properties.ControllerKind = "deployment"
-		properties.Controller = "deployment1"
-		properties.Pod = "pod1"
-		properties.Container = "container1"
-	} else {
-		properties = props
-	}
-
-	end := start.Add(resolution)
-
-	alloc := &Allocation{
-		Name:                  name,
-		Properties:            properties,
-		Window:                NewWindow(&start, &end).Clone(),
-		Start:                 start,
-		End:                   end,
-		CPUCoreHours:          1,
-		CPUCost:               1,
-		CPUCoreRequestAverage: 1,
-		CPUCoreUsageAverage:   1,
-		GPUHours:              1,
-		GPUCost:               1,
-		NetworkCost:           1,
-		LoadBalancerCost:      1,
-		PVs: PVAllocations{
-			disk: {
-				ByteHours: 1,
-				Cost:      1,
-			},
-		},
-		RAMByteHours:           1,
-		RAMCost:                1,
-		RAMBytesRequestAverage: 1,
-		RAMBytesUsageAverage:   1,
-		RawAllocationOnly: &RawAllocationOnlyData{
-			CPUCoreUsageMax:  1,
-			RAMBytesUsageMax: 1,
-		},
-	}
-
-	// If idle allocation, remove non-idle costs, but maintain total cost
-	if alloc.IsIdle() {
-		alloc.PVs = nil
-		alloc.NetworkCost = 0.0
-		alloc.LoadBalancerCost = 0.0
-		alloc.CPUCoreHours += 1.0
-		alloc.CPUCost += 1.0
-		alloc.RAMByteHours += 1.0
-		alloc.RAMCost += 1.0
-	}
-
-	return alloc
-}
-
 func TestAllocation_Add(t *testing.T) {
 	var nilAlloc *Allocation
 	zeroAlloc := &Allocation{}
@@ -559,390 +483,6 @@ func TestNewAllocationSet(t *testing.T) {
 	// TODO niko/etl
 }
 
-func generateAllocationSetClusterIdle(start time.Time) *AllocationSet {
-	// Cluster Idle allocations
-	a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster: "cluster1",
-	})
-	a1i.CPUCost = 5.0
-	a1i.RAMCost = 15.0
-	a1i.GPUCost = 0.0
-
-	a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster: "cluster2",
-	})
-	a2i.CPUCost = 5.0
-	a2i.RAMCost = 5.0
-	a2i.GPUCost = 0.0
-
-	as := generateAllocationSet(start)
-	as.Insert(a1i)
-	as.Insert(a2i)
-	return as
-}
-
-func generateAllocationSetNodeIdle(start time.Time) *AllocationSet {
-	// Node Idle allocations
-	a11i := NewUnitAllocation(fmt.Sprintf("c1nodes/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Node:       "c1nodes",
-		ProviderID: "c1nodes",
-	})
-	a11i.CPUCost = 5.0
-	a11i.RAMCost = 15.0
-	a11i.GPUCost = 0.0
-
-	a21i := NewUnitAllocation(fmt.Sprintf("node1/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Node:       "node1",
-		ProviderID: "node1",
-	})
-	a21i.CPUCost = 1.666667
-	a21i.RAMCost = 1.666667
-	a21i.GPUCost = 0.0
-
-	a22i := NewUnitAllocation(fmt.Sprintf("node2/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Node:       "node2",
-		ProviderID: "node2",
-	})
-	a22i.CPUCost = 1.666667
-	a22i.RAMCost = 1.666667
-	a22i.GPUCost = 0.0
-
-	a23i := NewUnitAllocation(fmt.Sprintf("node3/%s", IdleSuffix), start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Node:       "node3",
-		ProviderID: "node3",
-		Namespace: "",
-	})
-	a23i.CPUCost = 1.666667
-	a23i.RAMCost = 1.666667
-	a23i.GPUCost = 0.0
-
-	as := generateAllocationSet(start)
-	as.Insert(a11i)
-	as.Insert(a21i)
-	as.Insert(a22i)
-	as.Insert(a23i)
-	return as
-}
-
-func generateAllocationSet(start time.Time) *AllocationSet {
-
-	// Active allocations
-	a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace1",
-		Pod:        "pod1",
-		Container:  "container1",
-		ProviderID: "c1nodes",
-	})
-	a1111.RAMCost = 11.00
-
-	a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace1",
-		Pod:        "pod-abc",
-		Container:  "container2",
-		ProviderID: "c1nodes",
-	})
-
-	a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace1",
-		Pod:        "pod-def",
-		Container:  "container3",
-		ProviderID: "c1nodes",
-	})
-
-	a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace2",
-		Pod:        "pod-ghi",
-		Container:  "container4",
-		ProviderID: "c1nodes",
-	})
-
-	a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace2",
-		Pod:        "pod-ghi",
-		Container:  "container5",
-		ProviderID: "c1nodes",
-	})
-
-	a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &AllocationProperties{
-		Cluster:    "cluster1",
-		Namespace:  "namespace2",
-		Pod:        "pod-jkl",
-		Container:  "container6",
-		ProviderID: "c1nodes",
-	})
-
-	a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace2",
-		Pod:        "pod-mno",
-		Container:  "container4",
-		ProviderID: "node1",
-	})
-
-	a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace2",
-		Pod:        "pod-mno",
-		Container:  "container5",
-		ProviderID: "node1",
-	})
-
-	a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace2",
-		Pod:        "pod-pqr",
-		Container:  "container6",
-		ProviderID: "node2",
-	})
-
-	a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace3",
-		Pod:        "pod-stu",
-		Container:  "container7",
-		ProviderID: "node2",
-	})
-
-	a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace3",
-		Pod:        "pod-vwx",
-		Container:  "container8",
-		ProviderID: "node3",
-	})
-
-	a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &AllocationProperties{
-		Cluster:    "cluster2",
-		Namespace:  "namespace3",
-		Pod:        "pod-vwx",
-		Container:  "container9",
-		ProviderID: "node3",
-	})
-
-	// Controllers
-
-	a11abc2.Properties.ControllerKind = "deployment"
-	a11abc2.Properties.Controller = "deployment1"
-	a11def3.Properties.ControllerKind = "deployment"
-	a11def3.Properties.Controller = "deployment1"
-
-	a12ghi4.Properties.ControllerKind = "deployment"
-	a12ghi4.Properties.Controller = "deployment2"
-	a12ghi5.Properties.ControllerKind = "deployment"
-	a12ghi5.Properties.Controller = "deployment2"
-	a22mno4.Properties.ControllerKind = "deployment"
-	a22mno4.Properties.Controller = "deployment2"
-	a22mno5.Properties.ControllerKind = "deployment"
-	a22mno5.Properties.Controller = "deployment2"
-
-	a23stu7.Properties.ControllerKind = "deployment"
-	a23stu7.Properties.Controller = "deployment3"
-
-	a12jkl6.Properties.ControllerKind = "daemonset"
-	a12jkl6.Properties.Controller = "daemonset1"
-	a22pqr6.Properties.ControllerKind = "daemonset"
-	a22pqr6.Properties.Controller = "daemonset1"
-
-	a23vwx8.Properties.ControllerKind = "statefulset"
-	a23vwx8.Properties.Controller = "statefulset1"
-	a23vwx9.Properties.ControllerKind = "statefulset"
-	a23vwx9.Properties.Controller = "statefulset1"
-
-	// Labels
-
-	a1111.Properties.Labels = map[string]string{"app": "app1", "env": "env1"}
-	a12ghi4.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
-	a12ghi5.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
-	a22mno4.Properties.Labels = map[string]string{"app": "app2"}
-	a22mno5.Properties.Labels = map[string]string{"app": "app2"}
-
-	//Annotations
-	a23stu7.Properties.Annotations = map[string]string{"team": "team1"}
-	a23vwx8.Properties.Annotations = map[string]string{"team": "team2"}
-	a23vwx9.Properties.Annotations = map[string]string{"team": "team1"}
-
-	// Services
-	a12jkl6.Properties.Services = []string{"service1"}
-	a22pqr6.Properties.Services = []string{"service1"}
-
-	return NewAllocationSet(start, start.Add(day),
-		// cluster 1, namespace1
-		a1111, a11abc2, a11def3,
-		// cluster 1, namespace 2
-		a12ghi4, a12ghi5, a12jkl6,
-		// cluster 2, namespace 2
-		a22mno4, a22mno5, a22pqr6,
-		// cluster 2, namespace 3
-		a23stu7, a23vwx8, a23vwx9,
-	)
-}
-
-func generateAssetSets(start, end time.Time) []*AssetSet {
-	var assetSets []*AssetSet
-
-	// Create an AssetSet representing cluster costs for two clusters (cluster1
-	// and cluster2). Include Nodes and Disks for both, even though only
-	// Nodes will be counted. Whereas in practice, Assets should be aggregated
-	// by type, here we will provide multiple Nodes for one of the clusters to
-	// make sure the function still holds.
-
-	// NOTE: we're re-using generateAllocationSet so this has to line up with
-	// the allocated node costs from that function. See table above.
-
-	// | Hierarchy                               | Cost |  CPU |  RAM |  GPU | Adjustment |
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1:
-	//     nodes                                  100.00  55.00  44.00  11.00      -10.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 subtotal (adjusted)             100.00  50.00  40.00  10.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 allocated                        48.00   6.00  16.00   6.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 idle                             72.00  44.00  24.00   4.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2:
-	//     node1                                   35.00  20.00  15.00   0.00        0.00
-	//     node2                                   35.00  20.00  15.00   0.00        0.00
-	//     node3                                   30.00  10.00  10.00  10.00        0.00
-	//     (disks should not matter for idle)
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 subtotal                        100.00  50.00  40.00  10.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 allocated                        28.00   6.00   6.00   6.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 idle                             82.00  44.00  34.00   4.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-
-	cluster1Nodes := NewNode("c1nodes", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
-	cluster1Nodes.CPUCost = 55.0
-	cluster1Nodes.RAMCost = 44.0
-	cluster1Nodes.GPUCost = 11.0
-	cluster1Nodes.adjustment = -10.00
-	cluster1Nodes.CPUCoreHours = 8
-	cluster1Nodes.RAMByteHours = 6
-	cluster1Nodes.GPUHours = 24
-
-	cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
-	cluster2Node1.CPUCost = 20.0
-	cluster2Node1.RAMCost = 15.0
-	cluster2Node1.GPUCost = 0.0
-	cluster2Node1.CPUCoreHours = 4
-	cluster2Node1.RAMByteHours = 3
-	cluster2Node1.GPUHours = 0
-
-	cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
-	cluster2Node2.CPUCost = 20.0
-	cluster2Node2.RAMCost = 15.0
-	cluster2Node2.GPUCost = 0.0
-	cluster2Node2.CPUCoreHours = 3
-	cluster2Node2.RAMByteHours = 2
-	cluster2Node2.GPUHours = 0
-
-	cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
-	cluster2Node3.CPUCost = 10.0
-	cluster2Node3.RAMCost = 10.0
-	cluster2Node3.GPUCost = 10.0
-	cluster2Node3.CPUCoreHours = 2
-	cluster2Node3.RAMByteHours = 2
-	cluster2Node3.GPUHours = 24
-
-	cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
-	cluster2Disk1.Cost = 5.0
-	cluster2Disk1.adjustment = 1.0
-	cluster2Disk1.ByteHours = 5 * gb
-
-	cluster2Disk2 := NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
-	cluster2Disk2.Cost = 10.0
-	cluster2Disk2.adjustment = 3.0
-	cluster2Disk2.ByteHours = 10 * gb
-
-	assetSet1 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1, cluster2Disk2)
-	assetSets = append(assetSets, assetSet1)
-
-	// NOTE: we're re-using generateAllocationSet so this has to line up with
-	// the allocated node costs from that function. See table above.
-
-	// | Hierarchy                               | Cost |  CPU |  RAM |  GPU | Adjustment |
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1:
-	//     nodes                                  100.00   5.00   4.00   1.00       90.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 subtotal (adjusted)             100.00  50.00  40.00  10.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 allocated                        48.00   6.00  16.00   6.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster1 idle                             72.00  44.00  24.00   4.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2:
-	//     node1                                   35.00  20.00  15.00   0.00        0.00
-	//     node2                                   35.00  20.00  15.00   0.00        0.00
-	//     node3                                   30.00  10.00  10.00  10.00        0.00
-	//     (disks should not matter for idle)
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 subtotal                        100.00  50.00  40.00  10.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 allocated                        28.00   6.00   6.00   6.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-	//   cluster2 idle                             82.00  44.00  34.00   4.00        0.00
-	// +-----------------------------------------+------+------+------+------+------------+
-
-	cluster1Nodes = NewNode("", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
-	cluster1Nodes.CPUCost = 5.0
-	cluster1Nodes.RAMCost = 4.0
-	cluster1Nodes.GPUCost = 1.0
-	cluster1Nodes.adjustment = 90.00
-	cluster1Nodes.CPUCoreHours = 8
-	cluster1Nodes.RAMByteHours = 6
-	cluster1Nodes.GPUHours = 24
-
-	cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
-	cluster2Node1.CPUCost = 20.0
-	cluster2Node1.RAMCost = 15.0
-	cluster2Node1.GPUCost = 0.0
-	cluster2Node1.CPUCoreHours = 4
-	cluster2Node1.RAMByteHours = 3
-	cluster2Node1.GPUHours = 0
-
-	cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
-	cluster2Node2.CPUCost = 20.0
-	cluster2Node2.RAMCost = 15.0
-	cluster2Node2.GPUCost = 0.0
-	cluster2Node2.CPUCoreHours = 3
-	cluster2Node2.RAMByteHours = 2
-	cluster2Node2.GPUHours = 0
-
-	cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
-	cluster2Node3.CPUCost = 10.0
-	cluster2Node3.RAMCost = 10.0
-	cluster2Node3.GPUCost = 10.0
-	cluster2Node3.CPUCoreHours = 2
-	cluster2Node3.RAMByteHours = 2
-	cluster2Node3.GPUHours = 24
-
-	cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
-	cluster2Disk1.Cost = 5.0
-	cluster2Disk1.adjustment = 1.0
-	cluster2Disk1.ByteHours = 5 * gb
-
-	cluster2Disk2 = NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
-	cluster2Disk2.Cost = 12.0
-	cluster2Disk2.adjustment = 4.0
-	cluster2Disk2.ByteHours = 20 * gb
-
-	assetSet2 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1, cluster2Disk2)
-	assetSets = append(assetSets, assetSet2)
-	return assetSets
-}
-
 func assertAllocationSetTotals(t *testing.T, as *AllocationSet, msg string, err error, length int, totalCost float64) {
 	if err != nil {
 		t.Fatalf("AllocationSet.AggregateBy[%s]: unexpected error: %s", msg, err)
@@ -990,7 +530,7 @@ func printAllocationSet(msg string, as *AllocationSet) {
 
 func TestAllocationSet_AggregateBy(t *testing.T) {
 	// Test AggregateBy against the following workload topology, which is
-	// generated by generateAllocationSet:
+	// generated by GenerateAllocationSet:
 
 	// | Hierarchy                              | Cost |  CPU |  RAM |  GPU |   PV |  Net |  LB  |
 	// +----------------------------------------+------+------+------+------+------+------+------+
@@ -1992,7 +1532,6 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 			windowEnd:   endYesterday,
 			expMinutes:  1440.0,
 		},
-
 		// 7  Edge cases and errors
 
 		// 7a Empty AggregationProperties
@@ -2004,9 +1543,9 @@ func TestAllocationSet_AggregateBy(t *testing.T) {
 	for name, testcase := range cases {
 		t.Run(name, func(t *testing.T) {
 			if testcase.aggOpts != nil && testcase.aggOpts.IdleByNode {
-				as = generateAllocationSetNodeIdle(testcase.start)
+				as = GenerateAllocationSetNodeIdle(testcase.start)
 			} else {
-				as = generateAllocationSetClusterIdle(testcase.start)
+				as = GenerateAllocationSetClusterIdle(testcase.start)
 			}
 			err = as.AggregateBy(testcase.aggBy, testcase.aggOpts)
 			assertAllocationSetTotals(t, as, name, err, testcase.numResults, testcase.totalCost)
@@ -2028,9 +1567,9 @@ func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
 	start := end.Add(-day)
 
 	// Generate AllocationSet without idle allocations
-	as = generateAllocationSet(start)
+	as = GenerateAllocationSet(start)
 
-	assetSets := generateAssetSets(start, end)
+	assetSets := GenerateAssetSets(start, end)
 
 	cases := map[string]struct {
 		allocationSet *AllocationSet
@@ -2105,6 +1644,7 @@ func TestAllocationSet_ComputeIdleAllocations(t *testing.T) {
 }
 
 func TestAllocationSet_ComputeIdleAllocationsPerNode(t *testing.T) {
+
 	var as *AllocationSet
 	var err error
 	var idles map[string]*Allocation
@@ -2113,9 +1653,9 @@ func TestAllocationSet_ComputeIdleAllocationsPerNode(t *testing.T) {
 	start := end.Add(-day)
 
 	// Generate AllocationSet without idle allocations
-	as = generateAllocationSet(start)
+	as = GenerateAllocationSet(start)
 
-	assetSets := generateAssetSets(start, end)
+	assetSets := GenerateAssetSets(start, end)
 
 	cases := map[string]struct {
 		allocationSet *AllocationSet
@@ -2209,259 +1749,6 @@ func TestAllocationSet_ComputeIdleAllocationsPerNode(t *testing.T) {
 	}
 }
 
-func TestAllocationSet_ReconcileAllocations(t *testing.T) {
-	var as *AllocationSet
-	var err error
-
-	end := time.Now().UTC().Truncate(day)
-	start := end.Add(-day)
-
-	// Generate AllocationSet without idle allocations
-	as = generateAllocationSet(start)
-
-	// add reconcilable pvs to pod-mno
-	for _, a := range as.allocations {
-		if a.Properties.Pod == "pod-mno" {
-			a.PVs = a.PVs.Add(PVAllocations{
-				disk1: {
-					Cost:      2.5,
-					ByteHours: 2.5 * gb,
-				},
-				disk2: {
-					Cost:      5,
-					ByteHours: 5 * gb,
-				},
-			})
-		}
-	}
-
-	assetSets := generateAssetSets(start, end)
-
-	cases := map[string]struct {
-		allocationSet *AllocationSet
-		assetSet      *AssetSet
-		allocations   map[string]Allocation
-	}{
-		"1a": {
-			allocationSet: as,
-			assetSet:      assetSets[0],
-			allocations: map[string]Allocation{
-				// Allocation adjustments are found with the formula:
-				// ADJUSTMENT_RATE * NODE_COST * (ALLOC_HOURS / NODE_HOURS) - ALLOC_COST
-				// ADJUSTMENT_RATE: 0.90909090909
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    55	    |	  8	     |     1 	  |	  1
-				// RAM	|    44	    |	  6	     |     11 	  |	  1
-				// GPU	|    11	    |	 24      |     1 	  |	  1
-				"cluster1/namespace1/pod1/container1": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: -4.333333,
-					GPUCostAdjustment: -0.583333,
-				},
-				// ADJUSTMENT_RATE: 0.90909090909
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    55	    |	  8	     |     1 	  |	  1
-				// RAM	|    44	    |	  6	     |     1 	  |	  1
-				// GPU	|    11	    |	 24      |     1 	  |	  1
-				"cluster1/namespace1/pod-abc/container2": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace1/pod-def/container3": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-ghi/container4": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-ghi/container5": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-jkl/container6": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    20	    |	  4	     |     1 	  |	  1
-				// RAM	|    15	    |	  3	     |     1 	  |	  1
-				// GPU	|    0	    |	  0      |     1 	  |	  1
-				"cluster2/namespace2/pod-mno/container4": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -1.0,
-					PVCostAdjustment:  2.0,
-				},
-				"cluster2/namespace2/pod-mno/container5": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -1.0,
-					PVCostAdjustment:  2.0,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    20	    |	  3	     |     1 	  |	  1
-				// RAM	|    15	    |	  2	     |     1 	  |	  1
-				// GPU	|    0	    |	  0      |     1 	  |	  1
-				"cluster2/namespace2/pod-pqr/container6": {
-					CPUCostAdjustment: 5.666667,
-					RAMCostAdjustment: 6.5,
-					GPUCostAdjustment: -1.0,
-				},
-				"cluster2/namespace3/pod-stu/container7": {
-					CPUCostAdjustment: 5.666667,
-					RAMCostAdjustment: 6.5,
-					GPUCostAdjustment: -1.0,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    10	    |	  2	     |     1 	  |	  1
-				// RAM	|    10	    |	  2	     |     1 	  |	  1
-				// GPU	|    10	    |	 24      |     1 	  |	  1
-				"cluster2/namespace3/pod-vwx/container8": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster2/namespace3/pod-vwx/container9": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -0.583333,
-				},
-			},
-		},
-		"1b": {
-			allocationSet: as,
-			assetSet:      assetSets[1],
-			allocations: map[string]Allocation{
-				// ADJUSTMENT_RATE: 10
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|     5	    |	  8	     |     1 	  |	  1
-				// RAM	|     4	    |	  6	     |    11 	  |	  1
-				// GPU	|     1	    |	 24      |     1 	  |	  1
-				"cluster1/namespace1/pod1/container1": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: -4.333333,
-					GPUCostAdjustment: -0.583333,
-				},
-				// ADJUSTMENT_RATE: 10
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|     5	    |	  8	     |     1 	  |	  1
-				// RAM	|     4	    |	  6	     |     1 	  |	  1
-				// GPU	|     1	    |	 24      |     1 	  |	  1
-				"cluster1/namespace1/pod-abc/container2": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.6666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace1/pod-def/container3": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.6666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-ghi/container4": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.6666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-ghi/container5": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.6666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster1/namespace2/pod-jkl/container6": {
-					CPUCostAdjustment: 5.25,
-					RAMCostAdjustment: 5.6666667,
-					GPUCostAdjustment: -0.583333,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    20	    |	  4	     |     1 	  |	  1
-				// RAM	|    15	    |	  3	     |     1 	  |	  1
-				// GPU	|    0	    |	  0      |     1 	  |	  1
-				"cluster2/namespace2/pod-mno/container4": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -1.0,
-					PVCostAdjustment:  -0.5,
-				},
-				"cluster2/namespace2/pod-mno/container5": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -1.0,
-					PVCostAdjustment:  -0.5,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    20	    |	  3	     |     1 	  |	  1
-				// RAM	|    15	    |	  2	     |     1 	  |	  1
-				// GPU	|    0	    |	  0      |     1 	  |	  1
-				"cluster2/namespace2/pod-pqr/container6": {
-					CPUCostAdjustment: 5.666667,
-					RAMCostAdjustment: 6.5,
-					GPUCostAdjustment: -1.0,
-				},
-				"cluster2/namespace3/pod-stu/container7": {
-					CPUCostAdjustment: 5.666667,
-					RAMCostAdjustment: 6.5,
-					GPUCostAdjustment: -1.0,
-				},
-				// ADJUSTMENT_RATE: 1.0
-				// Type | NODE_COST | NODE_HOURs | ALLOC_COST | ALLOC_HOURS
-				// CPU	|    10	    |	  2	     |     1 	  |	  1
-				// RAM	|    10	    |	  2	     |     1 	  |	  1
-				// GPU	|    10	    |	 24      |     1 	  |	  1
-				"cluster2/namespace3/pod-vwx/container8": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -0.583333,
-				},
-				"cluster2/namespace3/pod-vwx/container9": {
-					CPUCostAdjustment: 4.0,
-					RAMCostAdjustment: 4.0,
-					GPUCostAdjustment: -0.583333,
-				},
-			},
-		},
-	}
-
-	for name, testcase := range cases {
-		t.Run(name, func(t *testing.T) {
-			err = as.Reconcile(testcase.assetSet)
-			reconAllocs := as.allocations
-			if err != nil {
-				t.Fatalf("unexpected error: %s", err)
-			}
-
-			for allocationName, testAlloc := range testcase.allocations {
-				if _, ok := reconAllocs[allocationName]; !ok {
-					t.Fatalf("expected allocation %s", allocationName)
-				}
-
-				if !util.IsApproximately(reconAllocs[allocationName].CPUCostAdjustment, testAlloc.CPUCostAdjustment) {
-					t.Fatalf("expected CPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.CPUCostAdjustment, reconAllocs[allocationName].CPUCostAdjustment)
-				}
-				if !util.IsApproximately(reconAllocs[allocationName].RAMCostAdjustment, testAlloc.RAMCostAdjustment) {
-					t.Fatalf("expected RAM Adjustment for %s to be %f; got %f", allocationName, testAlloc.RAMCostAdjustment, reconAllocs[allocationName].RAMCostAdjustment)
-				}
-				if !util.IsApproximately(reconAllocs[allocationName].GPUCostAdjustment, testAlloc.GPUCostAdjustment) {
-					t.Fatalf("expected GPU Adjustment for %s to be %f; got %f", allocationName, testAlloc.GPUCostAdjustment, reconAllocs[allocationName].GPUCostAdjustment)
-				}
-				if !util.IsApproximately(reconAllocs[allocationName].PVCostAdjustment, testAlloc.PVCostAdjustment) {
-					t.Fatalf("expected PV Adjustment for %s to be %f; got %f", allocationName, testAlloc.PVCostAdjustment, reconAllocs[allocationName].PVCostAdjustment)
-				}
-			}
-		})
-	}
-}
-
 // TODO niko/etl
 //func TestAllocationSet_Delete(t *testing.T) {}
 

+ 26 - 147
pkg/kubecost/asset_test.go

@@ -21,127 +21,6 @@ var windows = []Window{
 	NewWindow(&start3, &start4),
 }
 
-const gb = 1024 * 1024 * 1024
-
-// generateAssetSet generates the following topology:
-//
-// | Asset                        | Cost |  Adj |
-// +------------------------------+------+------+
-//   cluster1:
-//     node1:                        6.00   1.00
-//     node2:                        4.00   1.50
-//     node3:                        7.00  -0.50
-//     disk1:                        2.50   0.00
-//     disk2:                        1.50   0.00
-//     clusterManagement1:           3.00   0.00
-// +------------------------------+------+------+
-//   cluster1 subtotal              24.00   2.00
-// +------------------------------+------+------+
-//   cluster2:
-//     node4:                       12.00  -1.00
-//     disk3:                        2.50   0.00
-//     disk4:                        1.50   0.00
-//     clusterManagement2:           0.00   0.00
-// +------------------------------+------+------+
-//   cluster2 subtotal              16.00  -1.00
-// +------------------------------+------+------+
-//   cluster3:
-//     node5:                       17.00   2.00
-// +------------------------------+------+------+
-//   cluster3 subtotal              17.00   2.00
-// +------------------------------+------+------+
-//   total                          57.00   3.00
-// +------------------------------+------+------+
-func generateAssetSet(start time.Time) *AssetSet {
-	end := start.Add(day)
-	window := NewWindow(&start, &end)
-
-	hours := window.Duration().Hours()
-
-	node1 := NewNode("node1", "cluster1", "gcp-node1", *window.Clone().start, *window.Clone().end, window.Clone())
-	node1.CPUCost = 4.0
-	node1.RAMCost = 4.0
-	node1.GPUCost = 2.0
-	node1.Discount = 0.5
-	node1.CPUCoreHours = 2.0 * hours
-	node1.RAMByteHours = 4.0 * gb * hours
-	node1.GPUHours = 1.0 * hours
-	node1.SetAdjustment(1.0)
-	node1.SetLabels(map[string]string{"test": "test"})
-
-	node2 := NewNode("node2", "cluster1", "gcp-node2", *window.Clone().start, *window.Clone().end, window.Clone())
-	node2.CPUCost = 4.0
-	node2.RAMCost = 4.0
-	node2.GPUCost = 0.0
-	node2.Discount = 0.5
-	node2.CPUCoreHours = 2.0 * hours
-	node2.RAMByteHours = 4.0 * gb * hours
-	node2.GPUHours = 0.0 * hours
-	node2.SetAdjustment(1.5)
-
-	node3 := NewNode("node3", "cluster1", "gcp-node3", *window.Clone().start, *window.Clone().end, window.Clone())
-	node3.CPUCost = 4.0
-	node3.RAMCost = 4.0
-	node3.GPUCost = 3.0
-	node3.Discount = 0.5
-	node3.CPUCoreHours = 2.0 * hours
-	node3.RAMByteHours = 4.0 * gb * hours
-	node3.GPUHours = 2.0 * hours
-	node3.SetAdjustment(-0.5)
-
-	node4 := NewNode("node4", "cluster2", "gcp-node4", *window.Clone().start, *window.Clone().end, window.Clone())
-	node4.CPUCost = 10.0
-	node4.RAMCost = 6.0
-	node4.GPUCost = 0.0
-	node4.Discount = 0.25
-	node4.CPUCoreHours = 4.0 * hours
-	node4.RAMByteHours = 12.0 * gb * hours
-	node4.GPUHours = 0.0 * hours
-	node4.SetAdjustment(-1.0)
-
-	node5 := NewNode("node5", "cluster3", "aws-node5", *window.Clone().start, *window.Clone().end, window.Clone())
-	node5.CPUCost = 10.0
-	node5.RAMCost = 7.0
-	node5.GPUCost = 0.0
-	node5.Discount = 0.0
-	node5.CPUCoreHours = 8.0 * hours
-	node5.RAMByteHours = 24.0 * gb * hours
-	node5.GPUHours = 0.0 * hours
-	node5.SetAdjustment(2.0)
-
-	disk1 := NewDisk("disk1", "cluster1", "gcp-disk1", *window.Clone().start, *window.Clone().end, window.Clone())
-	disk1.Cost = 2.5
-	disk1.ByteHours = 100 * gb * hours
-
-	disk2 := NewDisk("disk2", "cluster1", "gcp-disk2", *window.Clone().start, *window.Clone().end, window.Clone())
-	disk2.Cost = 1.5
-	disk2.ByteHours = 60 * gb * hours
-
-	disk3 := NewDisk("disk3", "cluster2", "gcp-disk3", *window.Clone().start, *window.Clone().end, window.Clone())
-	disk3.Cost = 2.5
-	disk3.ByteHours = 100 * gb * hours
-
-	disk4 := NewDisk("disk4", "cluster2", "gcp-disk4", *window.Clone().start, *window.Clone().end, window.Clone())
-	disk4.Cost = 1.5
-	disk4.ByteHours = 100 * gb * hours
-
-	cm1 := NewClusterManagement("gcp", "cluster1", window.Clone())
-	cm1.Cost = 3.0
-
-	cm2 := NewClusterManagement("gcp", "cluster2", window.Clone())
-	cm2.Cost = 0.0
-
-	return NewAssetSet(
-		start, end,
-		// cluster 1
-		node1, node2, node3, disk1, disk2, cm1,
-		// cluster 2
-		node4, disk3, disk4, cm2,
-		// cluster 3
-		node5,
-	)
-}
-
 func assertAssetSet(t *testing.T, as *AssetSet, msg string, window Window, exps map[string]float64, err error) {
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy[%s]: unexpected error: %s", msg, err)
@@ -794,7 +673,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	// 1  Single-aggregation
 
 	// 1a []AssetProperty=[Cluster]
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{string(AssetClusterProp)}, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -806,7 +685,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	}, nil)
 
 	// 1b []AssetProperty=[Type]
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{string(AssetTypeProp)}, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -818,7 +697,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	}, nil)
 
 	// 1c []AssetProperty=[Nil]
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{}, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -828,7 +707,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	}, nil)
 
 	// 1d []AssetProperty=nil
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy(nil, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -848,7 +727,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	}, nil)
 
 	// 1e aggregateBy []string=["label:test"]
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{"label:test"}, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -861,7 +740,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	// 2  Multi-aggregation
 
 	// 2a []AssetProperty=[Cluster,Type]
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{string(AssetClusterProp), string(AssetTypeProp)}, nil)
 	if err != nil {
 		t.Fatalf("AssetSet.AggregateBy: unexpected error: %s", err)
@@ -879,7 +758,7 @@ func TestAssetSet_AggregateBy(t *testing.T) {
 	// 3  Share resources
 
 	// 3a Shared hourly cost > 0.0
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	err = as.AggregateBy([]string{string(AssetTypeProp)}, &AssetAggregationOptions{
 		SharedHourlyCosts: map[string]float64{"shared1": 0.5},
 	})
@@ -905,7 +784,7 @@ func TestAssetSet_FindMatch(t *testing.T) {
 	var err error
 
 	// Assert success of a simple match of Type and ProviderID
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	query = NewNode("", "", "gcp-node3", s, e, w)
 	match, err = as.FindMatch(query, []string{string(AssetTypeProp), string(AssetProviderIDProp)})
 	if err != nil {
@@ -913,7 +792,7 @@ func TestAssetSet_FindMatch(t *testing.T) {
 	}
 
 	// Assert error of a simple non-match of Type and ProviderID
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	query = NewNode("", "", "aws-node3", s, e, w)
 	match, err = as.FindMatch(query, []string{string(AssetTypeProp), string(AssetProviderIDProp)})
 	if err == nil {
@@ -921,7 +800,7 @@ func TestAssetSet_FindMatch(t *testing.T) {
 	}
 
 	// Assert error of matching ProviderID, but not Type
-	as = generateAssetSet(startYesterday)
+	as = GenerateAssetSet(startYesterday)
 	query = NewCloud(ComputeCategory, "gcp-node3", s, e, w)
 	match, err = as.FindMatch(query, []string{string(AssetTypeProp), string(AssetProviderIDProp)})
 	if err == nil {
@@ -944,9 +823,9 @@ func TestAssetSetRange_Accumulate(t *testing.T) {
 	var err error
 
 	asr = NewAssetSetRange(
-		generateAssetSet(startD0),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD0),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 	err = asr.AggregateBy(nil, nil)
 	as, err = asr.Accumulate()
@@ -968,9 +847,9 @@ func TestAssetSetRange_Accumulate(t *testing.T) {
 	}, nil)
 
 	asr = NewAssetSetRange(
-		generateAssetSet(startD0),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD0),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 	err = asr.AggregateBy([]string{}, nil)
 	as, err = asr.Accumulate()
@@ -982,9 +861,9 @@ func TestAssetSetRange_Accumulate(t *testing.T) {
 	}, nil)
 
 	asr = NewAssetSetRange(
-		generateAssetSet(startD0),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD0),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 	err = asr.AggregateBy([]string{string(AssetTypeProp)}, nil)
 	if err != nil {
@@ -1001,9 +880,9 @@ func TestAssetSetRange_Accumulate(t *testing.T) {
 	}, nil)
 
 	asr = NewAssetSetRange(
-		generateAssetSet(startD0),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD0),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 	err = asr.AggregateBy([]string{string(AssetClusterProp)}, nil)
 	if err != nil {
@@ -1023,8 +902,8 @@ func TestAssetSetRange_Accumulate(t *testing.T) {
 	// is empty (this was previously an issue)
 	asr = NewAssetSetRange(
 		NewAssetSet(startD0, startD1),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 
 	err = asr.AggregateBy([]string{string(AssetTypeProp)}, nil)
@@ -1186,7 +1065,7 @@ func TestAssetToExternalAllocation(t *testing.T) {
 // })
 // }
 
-// // generateAssetSet generates the following topology:
+// // GenerateAssetSet generates the following topology:
 // //
 // // | Asset                        | Cost |  Adj |
 // // +------------------------------+------+------+
@@ -1215,7 +1094,7 @@ func TestAssetToExternalAllocation(t *testing.T) {
 // // +------------------------------+------+------+
 // //   total                          57.00   3.00
 // // +------------------------------+------+------+
-// func generateAssetSet(start time.Time) *AssetSet {
+// func GenerateAssetSet(start time.Time) *AssetSet {
 // end := start.Add(day)
 // window := NewWindow(&start, &end)
 

+ 9 - 9
pkg/kubecost/kubecost_codecs_test.go

@@ -25,9 +25,9 @@ func BenchmarkAllocationSetRange_BinaryEncoding(b *testing.B) {
 	var err error
 
 	asr0 = NewAllocationSetRange(
-		generateAllocationSetClusterIdle(startD0),
-		generateAllocationSetClusterIdle(startD1),
-		generateAllocationSetClusterIdle(startD2),
+		GenerateAllocationSetClusterIdle(startD0),
+		GenerateAllocationSetClusterIdle(startD1),
+		GenerateAllocationSetClusterIdle(startD2),
 	)
 
 	for it := 0; it < b.N; it++ {
@@ -90,9 +90,9 @@ func TestAllocationSetRange_BinaryEncoding(t *testing.T) {
 	var err error
 
 	asr0 = NewAllocationSetRange(
-		generateAllocationSetClusterIdle(startD0),
-		generateAllocationSetClusterIdle(startD1),
-		generateAllocationSetClusterIdle(startD2),
+		GenerateAllocationSetClusterIdle(startD0),
+		GenerateAllocationSetClusterIdle(startD1),
+		GenerateAllocationSetClusterIdle(startD2),
 	)
 
 	bs, err = asr0.MarshalBinary()
@@ -212,9 +212,9 @@ func TestAssetSetRange_BinaryEncoding(t *testing.T) {
 	var err error
 
 	asr0 = NewAssetSetRange(
-		generateAssetSet(startD0),
-		generateAssetSet(startD1),
-		generateAssetSet(startD2),
+		GenerateAssetSet(startD0),
+		GenerateAssetSet(startD1),
+		GenerateAssetSet(startD2),
 	)
 
 	bs, err = asr0.MarshalBinary()

+ 667 - 0
pkg/kubecost/mock.go

@@ -0,0 +1,667 @@
+package kubecost
+
+import (
+	"fmt"
+	"time"
+)
+const gb = 1024 * 1024 * 1024
+const day = 24 * time.Hour
+var disk = PVKey{}
+
+// NewUnitAllocation creates an *Allocation with all of its float64 values set to 1 and generic properties if not provided in arg
+func NewUnitAllocation(name string, start time.Time, resolution time.Duration, props *AllocationProperties) *Allocation {
+	if name == "" {
+		name = "cluster1/namespace1/pod1/container1"
+	}
+
+	properties := &AllocationProperties{}
+	if props == nil {
+		properties.Cluster = "cluster1"
+		properties.Node = "node1"
+		properties.Namespace = "namespace1"
+		properties.ControllerKind = "deployment"
+		properties.Controller = "deployment1"
+		properties.Pod = "pod1"
+		properties.Container = "container1"
+	} else {
+		properties = props
+	}
+
+	end := start.Add(resolution)
+
+	alloc := &Allocation{
+		Name:                  name,
+		Properties:            properties,
+		Window:                NewWindow(&start, &end).Clone(),
+		Start:                 start,
+		End:                   end,
+		CPUCoreHours:          1,
+		CPUCost:               1,
+		CPUCoreRequestAverage: 1,
+		CPUCoreUsageAverage:   1,
+		GPUHours:              1,
+		GPUCost:               1,
+		NetworkCost:           1,
+		LoadBalancerCost:      1,
+		PVs: PVAllocations{
+			disk: {
+				ByteHours: 1,
+				Cost:      1,
+			},
+		},
+		RAMByteHours:           1,
+		RAMCost:                1,
+		RAMBytesRequestAverage: 1,
+		RAMBytesUsageAverage:   1,
+		RawAllocationOnly: &RawAllocationOnlyData{
+			CPUCoreUsageMax:  1,
+			RAMBytesUsageMax: 1,
+		},
+	}
+
+	// If idle allocation, remove non-idle costs, but maintain total cost
+	if alloc.IsIdle() {
+		alloc.PVs = nil
+		alloc.NetworkCost = 0.0
+		alloc.LoadBalancerCost = 0.0
+		alloc.CPUCoreHours += 1.0
+		alloc.CPUCost += 1.0
+		alloc.RAMByteHours += 1.0
+		alloc.RAMCost += 1.0
+	}
+
+	return alloc
+}
+
+// GenerateAllocationSetClusterIdle creates generic allocation set which includes an idle set broken down by cluster
+func GenerateAllocationSetClusterIdle(start time.Time) *AllocationSet {
+	// Cluster Idle allocations
+	a1i := NewUnitAllocation(fmt.Sprintf("cluster1/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster: "cluster1",
+	})
+	a1i.CPUCost = 5.0
+	a1i.RAMCost = 15.0
+	a1i.GPUCost = 0.0
+
+	a2i := NewUnitAllocation(fmt.Sprintf("cluster2/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster: "cluster2",
+	})
+	a2i.CPUCost = 5.0
+	a2i.RAMCost = 5.0
+	a2i.GPUCost = 0.0
+
+	as := GenerateAllocationSet(start)
+	as.Insert(a1i)
+	as.Insert(a2i)
+	return as
+}
+
+// GenerateAllocationSetNodeIdle creates generic allocation set which includes an idle set broken down by node
+func GenerateAllocationSetNodeIdle(start time.Time) *AllocationSet {
+	// Node Idle allocations
+	a11i := NewUnitAllocation(fmt.Sprintf("c1nodes/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Node:       "c1nodes",
+		ProviderID: "c1nodes",
+	})
+	a11i.CPUCost = 5.0
+	a11i.RAMCost = 15.0
+	a11i.GPUCost = 0.0
+
+	a21i := NewUnitAllocation(fmt.Sprintf("node1/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Node:       "node1",
+		ProviderID: "node1",
+	})
+	a21i.CPUCost = 1.666667
+	a21i.RAMCost = 1.666667
+	a21i.GPUCost = 0.0
+
+	a22i := NewUnitAllocation(fmt.Sprintf("node2/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Node:       "node2",
+		ProviderID: "node2",
+	})
+	a22i.CPUCost = 1.666667
+	a22i.RAMCost = 1.666667
+	a22i.GPUCost = 0.0
+
+	a23i := NewUnitAllocation(fmt.Sprintf("node3/%s", IdleSuffix), start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Node:       "node3",
+		ProviderID: "node3",
+		Namespace:  "",
+	})
+	a23i.CPUCost = 1.666667
+	a23i.RAMCost = 1.666667
+	a23i.GPUCost = 0.0
+
+	as := GenerateAllocationSet(start)
+	as.Insert(a11i)
+	as.Insert(a21i)
+	as.Insert(a22i)
+	as.Insert(a23i)
+	return as
+}
+
+// GenerateAllocationSet creates generic allocation set without idle allocations
+func GenerateAllocationSet(start time.Time) *AllocationSet {
+
+	// Active allocations
+	a1111 := NewUnitAllocation("cluster1/namespace1/pod1/container1", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace1",
+		Pod:        "pod1",
+		Container:  "container1",
+		ProviderID: "c1nodes",
+	})
+	a1111.RAMCost = 11.00
+
+	a11abc2 := NewUnitAllocation("cluster1/namespace1/pod-abc/container2", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace1",
+		Pod:        "pod-abc",
+		Container:  "container2",
+		ProviderID: "c1nodes",
+	})
+
+	a11def3 := NewUnitAllocation("cluster1/namespace1/pod-def/container3", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace1",
+		Pod:        "pod-def",
+		Container:  "container3",
+		ProviderID: "c1nodes",
+	})
+
+	a12ghi4 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container4", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace2",
+		Pod:        "pod-ghi",
+		Container:  "container4",
+		ProviderID: "c1nodes",
+	})
+
+	a12ghi5 := NewUnitAllocation("cluster1/namespace2/pod-ghi/container5", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace2",
+		Pod:        "pod-ghi",
+		Container:  "container5",
+		ProviderID: "c1nodes",
+	})
+
+	a12jkl6 := NewUnitAllocation("cluster1/namespace2/pod-jkl/container6", start, day, &AllocationProperties{
+		Cluster:    "cluster1",
+		Namespace:  "namespace2",
+		Pod:        "pod-jkl",
+		Container:  "container6",
+		ProviderID: "c1nodes",
+	})
+
+	a22mno4 := NewUnitAllocation("cluster2/namespace2/pod-mno/container4", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace2",
+		Pod:        "pod-mno",
+		Container:  "container4",
+		ProviderID: "node1",
+	})
+
+	a22mno5 := NewUnitAllocation("cluster2/namespace2/pod-mno/container5", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace2",
+		Pod:        "pod-mno",
+		Container:  "container5",
+		ProviderID: "node1",
+	})
+
+	a22pqr6 := NewUnitAllocation("cluster2/namespace2/pod-pqr/container6", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace2",
+		Pod:        "pod-pqr",
+		Container:  "container6",
+		ProviderID: "node2",
+	})
+
+	a23stu7 := NewUnitAllocation("cluster2/namespace3/pod-stu/container7", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace3",
+		Pod:        "pod-stu",
+		Container:  "container7",
+		ProviderID: "node2",
+	})
+
+	a23vwx8 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container8", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace3",
+		Pod:        "pod-vwx",
+		Container:  "container8",
+		ProviderID: "node3",
+	})
+
+	a23vwx9 := NewUnitAllocation("cluster2/namespace3/pod-vwx/container9", start, day, &AllocationProperties{
+		Cluster:    "cluster2",
+		Namespace:  "namespace3",
+		Pod:        "pod-vwx",
+		Container:  "container9",
+		ProviderID: "node3",
+	})
+
+	// Controllers
+
+	a11abc2.Properties.ControllerKind = "deployment"
+	a11abc2.Properties.Controller = "deployment1"
+	a11def3.Properties.ControllerKind = "deployment"
+	a11def3.Properties.Controller = "deployment1"
+
+	a12ghi4.Properties.ControllerKind = "deployment"
+	a12ghi4.Properties.Controller = "deployment2"
+	a12ghi5.Properties.ControllerKind = "deployment"
+	a12ghi5.Properties.Controller = "deployment2"
+	a22mno4.Properties.ControllerKind = "deployment"
+	a22mno4.Properties.Controller = "deployment2"
+	a22mno5.Properties.ControllerKind = "deployment"
+	a22mno5.Properties.Controller = "deployment2"
+
+	a23stu7.Properties.ControllerKind = "deployment"
+	a23stu7.Properties.Controller = "deployment3"
+
+	a12jkl6.Properties.ControllerKind = "daemonset"
+	a12jkl6.Properties.Controller = "daemonset1"
+	a22pqr6.Properties.ControllerKind = "daemonset"
+	a22pqr6.Properties.Controller = "daemonset1"
+
+	a23vwx8.Properties.ControllerKind = "statefulset"
+	a23vwx8.Properties.Controller = "statefulset1"
+	a23vwx9.Properties.ControllerKind = "statefulset"
+	a23vwx9.Properties.Controller = "statefulset1"
+
+	// Labels
+
+	a1111.Properties.Labels = map[string]string{"app": "app1", "env": "env1"}
+	a12ghi4.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
+	a12ghi5.Properties.Labels = map[string]string{"app": "app2", "env": "env2"}
+	a22mno4.Properties.Labels = map[string]string{"app": "app2"}
+	a22mno5.Properties.Labels = map[string]string{"app": "app2"}
+
+	//Annotations
+	a23stu7.Properties.Annotations = map[string]string{"team": "team1"}
+	a23vwx8.Properties.Annotations = map[string]string{"team": "team2"}
+	a23vwx9.Properties.Annotations = map[string]string{"team": "team1"}
+
+	// Services
+	a12jkl6.Properties.Services = []string{"service1"}
+	a22pqr6.Properties.Services = []string{"service1"}
+
+	return NewAllocationSet(start, start.Add(day),
+		// cluster 1, namespace1
+		a1111, a11abc2, a11def3,
+		// cluster 1, namespace 2
+		a12ghi4, a12ghi5, a12jkl6,
+		// cluster 2, namespace 2
+		a22mno4, a22mno5, a22pqr6,
+		// cluster 2, namespace 3
+		a23stu7, a23vwx8, a23vwx9,
+	)
+}
+
+// GenerateAllocationSetWithAssetProperties with no idle and connections to Assets in properties
+func GenerateAllocationSetWithAssetProperties(start time.Time)  *AllocationSet {
+	as := GenerateAllocationSet(start)
+	disk1 := PVKey{
+		Cluster: "cluster2",
+		Name:    "disk1",
+	}
+	disk2 := PVKey{
+		Cluster: "cluster2",
+		Name:    "disk2",
+	}
+	for _, a := range as.allocations {
+		// add reconcilable pvs to pod-mno
+		if a.Properties.Pod == "pod-mno" {
+			a.PVs = a.PVs.Add(PVAllocations{
+				disk1: {
+					Cost:      2.5,
+					ByteHours: 2.5 * gb,
+				},
+				disk2: {
+					Cost:      5,
+					ByteHours: 5 * gb,
+				},
+			})
+		}
+		// add loadBalancer service to allocations
+		if a.Name == "cluster2/namespace2/pod-mno/container4" {
+			a.Properties.Services = append(a.Properties.Services, "loadBalancer1")
+		}
+		if a.Name == "cluster2/namespace2/pod-mno/container5" {
+			a.Properties.Services = append(a.Properties.Services, "loadBalancer2")
+		}
+		if a.Name == "cluster2/namespace2/pod-pqr/container6" {
+			a.Properties.Services = append(a.Properties.Services, "loadBalancer1")
+			a.Properties.Services = append(a.Properties.Services, "loadBalancer2")
+		}
+	}
+	return as
+}
+
+// GenerateAssetSets creates generic AssetSets
+func GenerateAssetSets(start, end time.Time) []*AssetSet {
+	var assetSets []*AssetSet
+
+	// Create an AssetSet representing cluster costs for two clusters (cluster1
+	// and cluster2). Include Nodes and Disks for both, even though only
+	// Nodes will be counted. Whereas in practice, Assets should be aggregated
+	// by type, here we will provide multiple Nodes for one of the clusters to
+	// make sure the function still holds.
+
+	// NOTE: we're re-using GenerateAllocationSet so this has to line up with
+	// the allocated node costs from that function. See table above.
+
+	// | Hierarchy                               | Cost |  CPU |  RAM |  GPU | Adjustment |
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1:
+	//     nodes                                  100.00  55.00  44.00  11.00      -10.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 subtotal (adjusted)             100.00  50.00  40.00  10.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 allocated                        48.00   6.00  16.00   6.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 idle                             72.00  44.00  24.00   4.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2:
+	//     node1                                   35.00  20.00  15.00   0.00        0.00
+	//     node2                                   35.00  20.00  15.00   0.00        0.00
+	//     node3                                   30.00  10.00  10.00  10.00        0.00
+	//     (disks should not matter for idle)
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 subtotal                        100.00  50.00  40.00  10.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 allocated                        28.00   6.00   6.00   6.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 idle                             82.00  44.00  34.00   4.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+
+	cluster1Nodes := NewNode("c1nodes", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
+	cluster1Nodes.CPUCost = 55.0
+	cluster1Nodes.RAMCost = 44.0
+	cluster1Nodes.GPUCost = 11.0
+	cluster1Nodes.adjustment = -10.00
+	cluster1Nodes.CPUCoreHours = 8
+	cluster1Nodes.RAMByteHours = 6
+	cluster1Nodes.GPUHours = 24
+
+	cluster2Node1 := NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
+	cluster2Node1.CPUCost = 20.0
+	cluster2Node1.RAMCost = 15.0
+	cluster2Node1.GPUCost = 0.0
+	cluster2Node1.CPUCoreHours = 4
+	cluster2Node1.RAMByteHours = 3
+	cluster2Node1.GPUHours = 0
+
+	cluster2Node2 := NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
+	cluster2Node2.CPUCost = 20.0
+	cluster2Node2.RAMCost = 15.0
+	cluster2Node2.GPUCost = 0.0
+	cluster2Node2.CPUCoreHours = 3
+	cluster2Node2.RAMByteHours = 2
+	cluster2Node2.GPUHours = 0
+
+	cluster2Node3 := NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
+	cluster2Node3.CPUCost = 10.0
+	cluster2Node3.RAMCost = 10.0
+	cluster2Node3.GPUCost = 10.0
+	cluster2Node3.CPUCoreHours = 2
+	cluster2Node3.RAMByteHours = 2
+	cluster2Node3.GPUHours = 24
+
+	// Add PVs
+	cluster2Disk1 := NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
+	cluster2Disk1.Cost = 5.0
+	cluster2Disk1.adjustment = 1.0
+	cluster2Disk1.ByteHours = 5 * gb
+
+	cluster2Disk2 := NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
+	cluster2Disk2.Cost = 10.0
+	cluster2Disk2.adjustment = 3.0
+	cluster2Disk2.ByteHours = 10 * gb
+
+	cluster2Node1Disk := NewDisk("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
+	cluster2Node1Disk.Cost = 1.0
+	cluster2Node1Disk.ByteHours = 5 * gb
+
+	// Add Attached Disks
+	cluster2Node2Disk := NewDisk("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
+	cluster2Node2Disk.Cost = 2.0
+	cluster2Node2Disk.ByteHours = 5 * gb
+
+	cluster2Node3Disk := NewDisk("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
+	cluster2Node3Disk.Cost = 3.0
+	cluster2Node3Disk.ByteHours = 5 * gb
+
+	// Add Cluster Management
+	cluster1ClusterManagement := NewClusterManagement("", "cluster1", NewWindow(&start, &end))
+	cluster1ClusterManagement.Cost = 2.0
+
+	cluster2ClusterManagement := NewClusterManagement("", "cluster2", NewWindow(&start, &end))
+	cluster2ClusterManagement.Cost = 2.0
+
+	// Add Networks
+	c1Network := NewNetwork("", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
+	c1Network.Cost = 3.0
+
+	node1Network := NewNetwork("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
+	node1Network.Cost = 4.0
+
+	node2Network := NewNetwork("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
+	node2Network.Cost = 5.0
+
+	node3Network := NewNetwork("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
+	node3Network.Cost = 2.0
+
+	// Add LoadBalancers
+	cluster2LoadBalancer1 := NewLoadBalancer("namespace2/loadBalancer1", "cluster2", "lb1", start, end, NewWindow(&start, &end))
+	cluster2LoadBalancer1.Cost = 10.0
+
+	cluster2LoadBalancer2 := NewLoadBalancer("namespace2/loadBalancer2", "cluster2", "lb2", start, end, NewWindow(&start, &end))
+	cluster2LoadBalancer2.Cost = 15.0
+
+	assetSet1 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1,
+		cluster2Disk2, cluster2Node1Disk, cluster2Node2Disk, cluster2Node3Disk, cluster1ClusterManagement,
+		cluster2ClusterManagement, c1Network, node1Network, node2Network, node3Network, cluster2LoadBalancer1, cluster2LoadBalancer2)
+	assetSets = append(assetSets, assetSet1)
+
+	// NOTE: we're re-using GenerateAllocationSet so this has to line up with
+	// the allocated node costs from that function. See table above.
+
+	// | Hierarchy                               | Cost |  CPU |  RAM |  GPU | Adjustment |
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1:
+	//     nodes                                  100.00   5.00   4.00   1.00       90.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 subtotal (adjusted)             100.00  50.00  40.00  10.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 allocated                        48.00   6.00  16.00   6.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster1 idle                             72.00  44.00  24.00   4.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2:
+	//     node1                                   35.00  20.00  15.00   0.00        0.00
+	//     node2                                   35.00  20.00  15.00   0.00        0.00
+	//     node3                                   30.00  10.00  10.00  10.00        0.00
+	//     (disks should not matter for idle)
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 subtotal                        100.00  50.00  40.00  10.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 allocated                        28.00   6.00   6.00   6.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+	//   cluster2 idle                             82.00  44.00  34.00   4.00        0.00
+	// +-----------------------------------------+------+------+------+------+------------+
+
+	cluster1Nodes = NewNode("", "cluster1", "c1nodes", start, end, NewWindow(&start, &end))
+	cluster1Nodes.CPUCost = 5.0
+	cluster1Nodes.RAMCost = 4.0
+	cluster1Nodes.GPUCost = 1.0
+	cluster1Nodes.adjustment = 90.00
+	cluster1Nodes.CPUCoreHours = 8
+	cluster1Nodes.RAMByteHours = 6
+	cluster1Nodes.GPUHours = 24
+
+	cluster2Node1 = NewNode("node1", "cluster2", "node1", start, end, NewWindow(&start, &end))
+	cluster2Node1.CPUCost = 20.0
+	cluster2Node1.RAMCost = 15.0
+	cluster2Node1.GPUCost = 0.0
+	cluster2Node1.CPUCoreHours = 4
+	cluster2Node1.RAMByteHours = 3
+	cluster2Node1.GPUHours = 0
+
+	cluster2Node2 = NewNode("node2", "cluster2", "node2", start, end, NewWindow(&start, &end))
+	cluster2Node2.CPUCost = 20.0
+	cluster2Node2.RAMCost = 15.0
+	cluster2Node2.GPUCost = 0.0
+	cluster2Node2.CPUCoreHours = 3
+	cluster2Node2.RAMByteHours = 2
+	cluster2Node2.GPUHours = 0
+
+	cluster2Node3 = NewNode("node3", "cluster2", "node3", start, end, NewWindow(&start, &end))
+	cluster2Node3.CPUCost = 10.0
+	cluster2Node3.RAMCost = 10.0
+	cluster2Node3.GPUCost = 10.0
+	cluster2Node3.CPUCoreHours = 2
+	cluster2Node3.RAMByteHours = 2
+	cluster2Node3.GPUHours = 24
+
+	// Add PVs
+	cluster2Disk1 = NewDisk("disk1", "cluster2", "disk1", start, end, NewWindow(&start, &end))
+	cluster2Disk1.Cost = 5.0
+	cluster2Disk1.adjustment = 1.0
+	cluster2Disk1.ByteHours = 5 * gb
+
+	cluster2Disk2 = NewDisk("disk2", "cluster2", "disk2", start, end, NewWindow(&start, &end))
+	cluster2Disk2.Cost = 12.0
+	cluster2Disk2.adjustment = 4.0
+	cluster2Disk2.ByteHours = 20 * gb
+
+	assetSet2 := NewAssetSet(start, end, cluster1Nodes, cluster2Node1, cluster2Node2, cluster2Node3, cluster2Disk1,
+		cluster2Disk2, cluster2Node1Disk, cluster2Node2Disk, cluster2Node3Disk, cluster1ClusterManagement,
+		cluster2ClusterManagement, c1Network, node1Network, node2Network, node3Network, cluster2LoadBalancer1, cluster2LoadBalancer2)
+	assetSets = append(assetSets, assetSet2)
+	return assetSets
+}
+
+// GenerateAssetSet generates the following topology:
+//
+// | Asset                        | Cost |  Adj |
+// +------------------------------+------+------+
+//   cluster1:
+//     node1:                        6.00   1.00
+//     node2:                        4.00   1.50
+//     node3:                        7.00  -0.50
+//     disk1:                        2.50   0.00
+//     disk2:                        1.50   0.00
+//     clusterManagement1:           3.00   0.00
+// +------------------------------+------+------+
+//   cluster1 subtotal              24.00   2.00
+// +------------------------------+------+------+
+//   cluster2:
+//     node4:                       12.00  -1.00
+//     disk3:                        2.50   0.00
+//     disk4:                        1.50   0.00
+//     clusterManagement2:           0.00   0.00
+// +------------------------------+------+------+
+//   cluster2 subtotal              16.00  -1.00
+// +------------------------------+------+------+
+//   cluster3:
+//     node5:                       17.00   2.00
+// +------------------------------+------+------+
+//   cluster3 subtotal              17.00   2.00
+// +------------------------------+------+------+
+//   total                          57.00   3.00
+// +------------------------------+------+------+
+func GenerateAssetSet(start time.Time) *AssetSet {
+	end := start.Add(day)
+	window := NewWindow(&start, &end)
+
+	hours := window.Duration().Hours()
+
+	node1 := NewNode("node1", "cluster1", "gcp-node1", *window.Clone().start, *window.Clone().end, window.Clone())
+	node1.CPUCost = 4.0
+	node1.RAMCost = 4.0
+	node1.GPUCost = 2.0
+	node1.Discount = 0.5
+	node1.CPUCoreHours = 2.0 * hours
+	node1.RAMByteHours = 4.0 * gb * hours
+	node1.GPUHours = 1.0 * hours
+	node1.SetAdjustment(1.0)
+	node1.SetLabels(map[string]string{"test": "test"})
+
+	node2 := NewNode("node2", "cluster1", "gcp-node2", *window.Clone().start, *window.Clone().end, window.Clone())
+	node2.CPUCost = 4.0
+	node2.RAMCost = 4.0
+	node2.GPUCost = 0.0
+	node2.Discount = 0.5
+	node2.CPUCoreHours = 2.0 * hours
+	node2.RAMByteHours = 4.0 * gb * hours
+	node2.GPUHours = 0.0 * hours
+	node2.SetAdjustment(1.5)
+
+	node3 := NewNode("node3", "cluster1", "gcp-node3", *window.Clone().start, *window.Clone().end, window.Clone())
+	node3.CPUCost = 4.0
+	node3.RAMCost = 4.0
+	node3.GPUCost = 3.0
+	node3.Discount = 0.5
+	node3.CPUCoreHours = 2.0 * hours
+	node3.RAMByteHours = 4.0 * gb * hours
+	node3.GPUHours = 2.0 * hours
+	node3.SetAdjustment(-0.5)
+
+	node4 := NewNode("node4", "cluster2", "gcp-node4", *window.Clone().start, *window.Clone().end, window.Clone())
+	node4.CPUCost = 10.0
+	node4.RAMCost = 6.0
+	node4.GPUCost = 0.0
+	node4.Discount = 0.25
+	node4.CPUCoreHours = 4.0 * hours
+	node4.RAMByteHours = 12.0 * gb * hours
+	node4.GPUHours = 0.0 * hours
+	node4.SetAdjustment(-1.0)
+
+	node5 := NewNode("node5", "cluster3", "aws-node5", *window.Clone().start, *window.Clone().end, window.Clone())
+	node5.CPUCost = 10.0
+	node5.RAMCost = 7.0
+	node5.GPUCost = 0.0
+	node5.Discount = 0.0
+	node5.CPUCoreHours = 8.0 * hours
+	node5.RAMByteHours = 24.0 * gb * hours
+	node5.GPUHours = 0.0 * hours
+	node5.SetAdjustment(2.0)
+
+	disk1 := NewDisk("disk1", "cluster1", "gcp-disk1", *window.Clone().start, *window.Clone().end, window.Clone())
+	disk1.Cost = 2.5
+	disk1.ByteHours = 100 * gb * hours
+
+	disk2 := NewDisk("disk2", "cluster1", "gcp-disk2", *window.Clone().start, *window.Clone().end, window.Clone())
+	disk2.Cost = 1.5
+	disk2.ByteHours = 60 * gb * hours
+
+	disk3 := NewDisk("disk3", "cluster2", "gcp-disk3", *window.Clone().start, *window.Clone().end, window.Clone())
+	disk3.Cost = 2.5
+	disk3.ByteHours = 100 * gb * hours
+
+	disk4 := NewDisk("disk4", "cluster2", "gcp-disk4", *window.Clone().start, *window.Clone().end, window.Clone())
+	disk4.Cost = 1.5
+	disk4.ByteHours = 100 * gb * hours
+
+	cm1 := NewClusterManagement("gcp", "cluster1", window.Clone())
+	cm1.Cost = 3.0
+
+	cm2 := NewClusterManagement("gcp", "cluster2", window.Clone())
+	cm2.Cost = 0.0
+
+	return NewAssetSet(
+		start, end,
+		// cluster 1
+		node1, node2, node3, disk1, disk2, cm1,
+		// cluster 2
+		node4, disk3, disk4, cm2,
+		// cluster 3
+		node5,
+	)
+}