|
@@ -1676,281 +1676,6 @@ func (as *AllocationSet) Clone() *AllocationSet {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ComputeIdleAllocations computes the idle allocations for the AllocationSet,
|
|
|
|
|
-// given a set of Assets. Ideally, assetSet should contain only Nodes, but if
|
|
|
|
|
-// it contains other Assets, they will be ignored; only CPU, GPU and RAM are
|
|
|
|
|
-// considered for idle allocation. If the Nodes have adjustments, then apply
|
|
|
|
|
-// the adjustments proportionally to each of the resources so that total
|
|
|
|
|
-// allocation with idle reflects the adjusted node costs. One idle allocation
|
|
|
|
|
-// per-cluster will be computed and returned, keyed by cluster_id.
|
|
|
|
|
-func (as *AllocationSet) ComputeIdleAllocations(assetSet *AssetSet) (map[string]*Allocation, error) {
|
|
|
|
|
- if as == nil {
|
|
|
|
|
- return nil, fmt.Errorf("cannot compute idle allocation for nil AllocationSet")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if assetSet == nil {
|
|
|
|
|
- return nil, fmt.Errorf("cannot compute idle allocation with nil AssetSet")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if !as.Window.Equal(assetSet.Window) {
|
|
|
|
|
- return nil, fmt.Errorf("cannot compute idle allocation for sets with mismatched windows: %s != %s", as.Window, assetSet.Window)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- window := as.Window
|
|
|
|
|
-
|
|
|
|
|
- // Build a map of cumulative cluster asset costs, per resource; i.e.
|
|
|
|
|
- // cluster-to-{cpu|gpu|ram}-to-cost.
|
|
|
|
|
- assetClusterResourceCosts := map[string]map[string]float64{}
|
|
|
|
|
- assetSet.Each(func(key string, a Asset) {
|
|
|
|
|
- if node, ok := a.(*Node); ok {
|
|
|
|
|
- if _, ok := assetClusterResourceCosts[node.Properties().Cluster]; !ok {
|
|
|
|
|
- assetClusterResourceCosts[node.Properties().Cluster] = map[string]float64{}
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 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
|
|
|
|
|
- log.DedupedWarningf(5, "Compute Idle Allocations: Node Cost Adjusted to $0.00 for %s", node.properties.Name)
|
|
|
|
|
- } 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())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cpuCost := node.CPUCost * (1.0 - node.Discount) * adjustmentRate
|
|
|
|
|
- ramCost := node.RAMCost * (1.0 - node.Discount) * adjustmentRate
|
|
|
|
|
- gpuCost := node.GPUCost * (1.0) * adjustmentRate
|
|
|
|
|
-
|
|
|
|
|
- assetClusterResourceCosts[node.Properties().Cluster]["cpu"] += cpuCost
|
|
|
|
|
- assetClusterResourceCosts[node.Properties().Cluster]["ram"] += ramCost
|
|
|
|
|
- assetClusterResourceCosts[node.Properties().Cluster]["gpu"] += gpuCost
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // Determine start, end on a per-cluster basis
|
|
|
|
|
- clusterStarts := map[string]time.Time{}
|
|
|
|
|
- clusterEnds := map[string]time.Time{}
|
|
|
|
|
-
|
|
|
|
|
- // Subtract allocated costs from asset costs, leaving only the remaining
|
|
|
|
|
- // idle costs.
|
|
|
|
|
- as.Each(func(name string, a *Allocation) {
|
|
|
|
|
- cluster := a.Properties.Cluster
|
|
|
|
|
- if cluster == "" {
|
|
|
|
|
- // Failed to find allocation's cluster
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if _, ok := assetClusterResourceCosts[cluster]; !ok {
|
|
|
|
|
- // Failed to find assets for allocation's cluster
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Set cluster (start, end) if they are either not currently set,
|
|
|
|
|
- // or if the detected (start, end) of the current allocation falls
|
|
|
|
|
- // before or after, respectively, the current values.
|
|
|
|
|
- if s, ok := clusterStarts[cluster]; !ok || a.Start.Before(s) {
|
|
|
|
|
- clusterStarts[cluster] = a.Start
|
|
|
|
|
- }
|
|
|
|
|
- if e, ok := clusterEnds[cluster]; !ok || a.End.After(e) {
|
|
|
|
|
- clusterEnds[cluster] = a.End
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- assetClusterResourceCosts[cluster]["cpu"] -= a.CPUTotalCost()
|
|
|
|
|
- assetClusterResourceCosts[cluster]["gpu"] -= a.GPUTotalCost()
|
|
|
|
|
- assetClusterResourceCosts[cluster]["ram"] -= a.RAMTotalCost()
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // Turn remaining un-allocated asset costs into idle allocations
|
|
|
|
|
- idleAllocs := map[string]*Allocation{}
|
|
|
|
|
- for cluster, resources := range assetClusterResourceCosts {
|
|
|
|
|
- // Default start and end to the (start, end) of the given window, but
|
|
|
|
|
- // use the actual, detected (start, end) pair if they are available.
|
|
|
|
|
- start := *window.Start()
|
|
|
|
|
- if s, ok := clusterStarts[cluster]; ok && window.Contains(s) {
|
|
|
|
|
- start = s
|
|
|
|
|
- }
|
|
|
|
|
- end := *window.End()
|
|
|
|
|
- if e, ok := clusterEnds[cluster]; ok && window.Contains(e) {
|
|
|
|
|
- end = e
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- idleAlloc := &Allocation{
|
|
|
|
|
- Name: fmt.Sprintf("%s/%s", cluster, IdleSuffix),
|
|
|
|
|
- Window: window.Clone(),
|
|
|
|
|
- Properties: &AllocationProperties{Cluster: cluster},
|
|
|
|
|
- Start: start,
|
|
|
|
|
- End: end,
|
|
|
|
|
- CPUCost: resources["cpu"],
|
|
|
|
|
- GPUCost: resources["gpu"],
|
|
|
|
|
- RAMCost: resources["ram"],
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Do not continue if multiple idle allocations are computed for a
|
|
|
|
|
- // single cluster.
|
|
|
|
|
- if _, ok := idleAllocs[cluster]; ok {
|
|
|
|
|
- return nil, fmt.Errorf("duplicate idle allocations for cluster %s", cluster)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- idleAllocs[cluster] = idleAlloc
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return idleAllocs, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// TODO etl -- deprecate!
|
|
|
|
|
-
|
|
|
|
|
-// ComputeIdleAllocationsByNode computes the idle allocations for the AllocationSet,
|
|
|
|
|
-// given a set of Assets. Ideally, assetSet should contain only Nodes, but if
|
|
|
|
|
-// it contains other Assets, they will be ignored; only CPU, GPU and RAM are
|
|
|
|
|
-// considered for idle allocation. If the Nodes have adjustments, then apply
|
|
|
|
|
-// the adjustments proportionally to each of the resources so that total
|
|
|
|
|
-// 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")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if assetSet == nil {
|
|
|
|
|
- return nil, fmt.Errorf("cannot compute idle allocation with nil AssetSet")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if !as.Window.Equal(assetSet.Window) {
|
|
|
|
|
- return nil, fmt.Errorf("cannot compute idle allocation for sets with mismatched windows: %s != %s", as.Window, assetSet.Window)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- window := as.Window
|
|
|
|
|
-
|
|
|
|
|
- // Build a map of cumulative cluster asset costs, per resource; i.e.
|
|
|
|
|
- // cluster-to-{cpu|gpu|ram}-to-cost.
|
|
|
|
|
- assetNodeResourceCosts := map[string]map[string]float64{}
|
|
|
|
|
- nodesByProviderId := map[string]*Node{}
|
|
|
|
|
- assetSet.Each(func(key string, a Asset) {
|
|
|
|
|
- if node, ok := a.(*Node); ok {
|
|
|
|
|
- if _, ok := assetNodeResourceCosts[node.Properties().ProviderID]; ok || node.Properties().ProviderID == "" {
|
|
|
|
|
- log.DedupedWarningf(5, "Compute Idle Allocations By Node: Node missing providerId: %s", node.properties.Name)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- nodesByProviderId[node.Properties().ProviderID] = node
|
|
|
|
|
- assetNodeResourceCosts[node.Properties().ProviderID] = map[string]float64{}
|
|
|
|
|
-
|
|
|
|
|
- // 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
|
|
|
|
|
- log.DedupedWarningf(5, "Compute Idle Allocations: Node Cost Adjusted to $0.00 for %s", node.properties.Name)
|
|
|
|
|
- } 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())
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- cpuCost := node.CPUCost * (1.0 - node.Discount) * adjustmentRate
|
|
|
|
|
- ramCost := node.RAMCost * (1.0 - node.Discount) * adjustmentRate
|
|
|
|
|
- gpuCost := node.GPUCost * adjustmentRate
|
|
|
|
|
-
|
|
|
|
|
- assetNodeResourceCosts[node.Properties().ProviderID]["cpu"] += cpuCost
|
|
|
|
|
- assetNodeResourceCosts[node.Properties().ProviderID]["ram"] += ramCost
|
|
|
|
|
- assetNodeResourceCosts[node.Properties().ProviderID]["gpu"] += gpuCost
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // Determine start, end on a per-cluster basis
|
|
|
|
|
- nodeStarts := map[string]time.Time{}
|
|
|
|
|
- nodeEnds := map[string]time.Time{}
|
|
|
|
|
-
|
|
|
|
|
- // Subtract allocated costs from asset costs, leaving only the remaining
|
|
|
|
|
- // idle costs.
|
|
|
|
|
- as.Each(func(name string, a *Allocation) {
|
|
|
|
|
- providerId := a.Properties.ProviderID
|
|
|
|
|
- if providerId == "" {
|
|
|
|
|
- // Failed to find allocation's node
|
|
|
|
|
- log.DedupedWarningf(5, "Compute Idle Allocations By Node: Allocation missing providerId: %s", a.Name)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if _, ok := assetNodeResourceCosts[providerId]; !ok {
|
|
|
|
|
- // Failed to find assets for allocation's node
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Set cluster (start, end) if they are either not currently set,
|
|
|
|
|
- // or if the detected (start, end) of the current allocation falls
|
|
|
|
|
- // before or after, respectively, the current values.
|
|
|
|
|
- if s, ok := nodeStarts[providerId]; !ok || a.Start.Before(s) {
|
|
|
|
|
- nodeStarts[providerId] = a.Start
|
|
|
|
|
- }
|
|
|
|
|
- if e, ok := nodeEnds[providerId]; !ok || a.End.After(e) {
|
|
|
|
|
- nodeEnds[providerId] = a.End
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- assetNodeResourceCosts[providerId]["cpu"] -= a.CPUTotalCost()
|
|
|
|
|
- assetNodeResourceCosts[providerId]["gpu"] -= a.GPUTotalCost()
|
|
|
|
|
- assetNodeResourceCosts[providerId]["ram"] -= a.RAMTotalCost()
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- // Turn remaining un-allocated asset costs into idle allocations
|
|
|
|
|
- idleAllocs := map[string]*Allocation{}
|
|
|
|
|
- for providerId, resources := range assetNodeResourceCosts {
|
|
|
|
|
- // Default start and end to the (start, end) of the given window, but
|
|
|
|
|
- // use the actual, detected (start, end) pair if they are available.
|
|
|
|
|
- start := *window.Start()
|
|
|
|
|
- if s, ok := nodeStarts[providerId]; ok && window.Contains(s) {
|
|
|
|
|
- start = s
|
|
|
|
|
- }
|
|
|
|
|
- end := *window.End()
|
|
|
|
|
- if e, ok := nodeEnds[providerId]; ok && window.Contains(e) {
|
|
|
|
|
- end = e
|
|
|
|
|
- }
|
|
|
|
|
- node := nodesByProviderId[providerId]
|
|
|
|
|
- idleAlloc := &Allocation{
|
|
|
|
|
- Name: fmt.Sprintf("%s/%s", node.properties.Name, IdleSuffix),
|
|
|
|
|
- Window: window.Clone(),
|
|
|
|
|
- Properties: &AllocationProperties{
|
|
|
|
|
- Cluster: node.properties.Cluster,
|
|
|
|
|
- Node: node.properties.Name,
|
|
|
|
|
- ProviderID: providerId,
|
|
|
|
|
- },
|
|
|
|
|
- Start: start,
|
|
|
|
|
- End: end,
|
|
|
|
|
- CPUCost: resources["cpu"],
|
|
|
|
|
- GPUCost: resources["gpu"],
|
|
|
|
|
- RAMCost: resources["ram"],
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // Do not continue if multiple idle allocations are computed for a
|
|
|
|
|
- // single node.
|
|
|
|
|
- if _, ok := idleAllocs[providerId]; ok {
|
|
|
|
|
- return nil, fmt.Errorf("duplicate idle allocations for node Provider ID: %s", providerId)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- idleAllocs[providerId] = idleAlloc
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return idleAllocs, nil
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// Delete removes the allocation with the given name from the set
|
|
// Delete removes the allocation with the given name from the set
|
|
|
func (as *AllocationSet) Delete(name string) {
|
|
func (as *AllocationSet) Delete(name string) {
|
|
|
if as == nil {
|
|
if as == nil {
|