Skip to content

Commit

Permalink
Add ephemeral storage price to NodePrice
Browse files Browse the repository at this point in the history
  • Loading branch information
yaroslava-serdiuk committed Jun 3, 2022
1 parent 6acc9d1 commit 581f1d7
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 4 deletions.
42 changes: 39 additions & 3 deletions cluster-autoscaler/cloudprovider/gce/gce_price_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package gce

import (
"math"
"strconv"
"strings"
"time"

Expand All @@ -43,10 +44,15 @@ func NewGcePriceModel(info PriceInfo, ephemeralStorageSupport bool) *GcePriceMod
}

const (
preemptibleLabel = "cloud.google.com/gke-preemptible"
spotLabel = "cloud.google.com/gke-spot"
preemptibleLabel = "cloud.google.com/gke-preemptible"
spotLabel = "cloud.google.com/gke-spot"
ephemeralStorageLocalSsdLabel = "cloud.google.com/gke-ephemeral-storage-local-ssd"
bootDiskTypeLabel = "cloud.google.com/gke-boot-disk"
)

// DefaultBootDiskSize is 100 GB.
const DefaultBootDiskSize = 100 * units.GB

// NodePrice returns a price of running the given node for a given period of time.
// All prices are in USD.
func (model *GcePriceModel) NodePrice(node *apiv1.Node, startTime time.Time, endTime time.Time) (float64, error) {
Expand Down Expand Up @@ -75,6 +81,37 @@ func (model *GcePriceModel) NodePrice(node *apiv1.Node, startTime time.Time, end
}
}

// Ephemeral Storage
if model.EphemeralStorageSupport {
// Local SSD price
if node.Labels[ephemeralStorageLocalSsdLabel] == "true" || node.Annotations[EphemeralStorageLocalSsdAnnotation] == "true" {
localSsdCount, _ := strconv.ParseFloat(node.Annotations[LocalSsdCountAnnotation], 64)
localSsdPrice := model.PriceInfo.LocalSsdPricePerHour()
if hasPreemptiblePricing(node) {
localSsdPrice = model.PriceInfo.SpotLocalSsdPricePerHour()
}
price += localSsdCount * float64(LocalSSDDiskSizeInGiB) * localSsdPrice * getHours(startTime, endTime)
}

// Boot disk price
bootDiskSize, _ := strconv.ParseInt(node.Annotations[BootDiskSizeAnnotation], 10, 64)
if bootDiskSize == 0 {
klog.Error("Boot disk size is not found for node %s, using default size %v", node.Name, DefaultBootDiskSize)
bootDiskSize = DefaultBootDiskSize
}
bootDiskType := node.Annotations[BootDiskTypeAnnotation]
if val, ok := node.Labels[bootDiskTypeLabel]; ok {
bootDiskType = val
}
if bootDiskType == "" {
klog.Error("Boot disk type is not found for node %s, using default type %s", node.Name, DefaultBootDiskType)
bootDiskType = DefaultBootDiskType
}
bootDiskPrice := model.PriceInfo.BootDiskPricePerHour()[bootDiskType]

price += bootDiskPrice * float64(bootDiskSize) * getHours(startTime, endTime)
}

// GPUs
if gpuRequest, found := node.Status.Capacity[gpu.ResourceNvidiaGPU]; found {
gpuPrice := model.PriceInfo.BaseGpuPricePerHour()
Expand All @@ -94,7 +131,6 @@ func (model *GcePriceModel) NodePrice(node *apiv1.Node, startTime time.Time, end
price += float64(gpuRequest.MilliValue()) / 1000.0 * gpuPrice * getHours(startTime, endTime)
}

// TODO: handle SSDs.
return price, nil
}

Expand Down
43 changes: 42 additions & 1 deletion cluster-autoscaler/cloudprovider/gce/gce_price_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package gce

import (
"math"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -58,13 +59,34 @@ func testNode(t *testing.T, nodeName string, instanceType string, millicpu int64
return node
}

// testNodeEphemeralStorage builds node that includes information about ephemeral storage in annotations.
func testNodeEphemeralStorage(t *testing.T, nodeName string, isEphemeralStorageLocalSsd bool, localSsdCount int, bootDiskType string, bootDiskSize int, isSpot bool) *apiv1.Node {
node := testNode(t, nodeName, "", 8000, 30*units.GiB, "", 0, false, isSpot)
if isEphemeralStorageLocalSsd {
AddEphemeralStorageToNode(node, int64(localSsdCount)*LocalSSDDiskSizeInGiB)
} else {
AddEphemeralStorageToNode(node, int64(bootDiskSize))
}
if isEphemeralStorageLocalSsd {
node.Labels[ephemeralStorageLocalSsdLabel] = "true"
}
node.Annotations = make(map[string]string)
if localSsdCount > 0 {
node.Annotations[LocalSsdCountAnnotation] = strconv.Itoa(localSsdCount)
}
node.Annotations[BootDiskSizeAnnotation] = strconv.Itoa(bootDiskSize)
node.Annotations[BootDiskTypeAnnotation] = bootDiskType
return node
}

// this test is meant to cover all the branches in pricing logic, not all possible types of instances
func TestGetNodePrice(t *testing.T) {
// tests assert that price(cheaperNode) < priceComparisonCoefficient * price(expensiveNode)
cases := map[string]struct {
cheaperNode *apiv1.Node
expensiveNode *apiv1.Node
priceComparisonCoefficient float64
expanderSupport bool
}{
// instance types
"e2 is cheaper than n1": {
Expand Down Expand Up @@ -172,11 +194,30 @@ func TestGetNodePrice(t *testing.T) {
expensiveNode: testNode(t, "known", "n1-custom", 8000, 30*units.GiB, "", 0, false, false),
priceComparisonCoefficient: 1.001,
},
// Ephemeral storage support
"ephemeral storage support: less local SSD count is cheaper": {
cheaperNode: testNodeEphemeralStorage(t, "cheapNode", true, 2, "pd-standard", 100, false),
expensiveNode: testNodeEphemeralStorage(t, "expensiveNode", true, 4, "pd-standard", 100, false),
priceComparisonCoefficient: 1,
expanderSupport: true,
},
"ephemeral storage support: local SSD cheaper than boot disk": {
cheaperNode: testNodeEphemeralStorage(t, "cheapNode", true, 1, "pd-standard", 100, true),
expensiveNode: testNodeEphemeralStorage(t, "expensiveNode", false, 0, "pd-ssd", 100, false),
priceComparisonCoefficient: 1,
expanderSupport: true,
},
"ephemeral storage support: node with cheaper boot disk option is cheaper": {
cheaperNode: testNodeEphemeralStorage(t, "cheapNode", false, 0, "pd-standard", 100, false),
expensiveNode: testNodeEphemeralStorage(t, "expensiveNode", false, 0, "pd-ssd", 100, false),
priceComparisonCoefficient: 1,
expanderSupport: true,
},
}

for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
model := NewGcePriceModel(NewGcePriceInfo(), false)
model := NewGcePriceModel(NewGcePriceInfo(), tc.expanderSupport)
now := time.Now()

price1, err := model.NodePrice(tc.cheaperNode, now, now.Add(time.Hour))
Expand Down
9 changes: 9 additions & 0 deletions cluster-autoscaler/utils/test/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ func BuildTestNode(name string, millicpu int64, mem int64) *apiv1.Node {
return node
}

// AddEphemeralStorageToNode adds ephemeral storage capacity to a given node.
func AddEphemeralStorageToNode(node *apiv1.Node, eph int64) *apiv1.Node {
if eph >= 0 {
node.Status.Capacity[apiv1.ResourceEphemeralStorage] = *resource.NewQuantity(eph, resource.DecimalSI)
node.Status.Allocatable[apiv1.ResourceEphemeralStorage] = *resource.NewQuantity(eph, resource.DecimalSI)
}
return node
}

// AddGpusToNode adds GPU capacity to given node. Default accelerator type is used.
func AddGpusToNode(node *apiv1.Node, gpusCount int64) {
node.Spec.Taints = append(
Expand Down

0 comments on commit 581f1d7

Please sign in to comment.