Skip to content

Commit

Permalink
Support launching spot instances when missing on-demand pricing data
Browse files Browse the repository at this point in the history
  • Loading branch information
cristim committed Sep 7, 2018
1 parent cd63b76 commit d343dfb
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 47 deletions.
29 changes: 15 additions & 14 deletions core/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d

for _, candidate := range i.region.instanceTypeInformation {

logger.Println("Comparing ", candidate.instanceType, " with ",
current.instanceType)
logger.Printf("Comparing %s with %s ",
current.instanceType, candidate.instanceType)

candidatePrice := i.calculatePrice(candidate)

Expand All @@ -307,7 +307,7 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
bestPrice = candidatePrice
chosenSpotType = candidate.instanceType
cheapest = candidate
debug.Println("Best option is now: ", chosenSpotType, " at ", bestPrice)
logger.Println("Found compatible instance type: ", chosenSpotType, " at ", bestPrice)
} else if chosenSpotType != "" {
debug.Println("Current best option: ", chosenSpotType, " at ", bestPrice)
}
Expand All @@ -320,7 +320,7 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
}

func (i *instance) launchSpotReplacement() error {
instanceType, err := i.getCheapestCompatibleSpotInstanceType(
spotInstanceType, err := i.getCheapestCompatibleSpotInstanceType(
i.asg.getAllowedInstanceTypes(i),
i.asg.getDisallowedInstanceTypes(i))

Expand All @@ -329,10 +329,9 @@ func (i *instance) launchSpotReplacement() error {
return err
}

bidPrice := i.getPricetoBid(instanceType.pricing.onDemand,
instanceType.pricing.spot[*i.Placement.AvailabilityZone])
bidPrice := i.getPricetoBid(spotInstanceType.pricing.spot[*i.Placement.AvailabilityZone])

runInstancesInput := i.createRunInstancesInput(instanceType.instanceType, bidPrice)
runInstancesInput := i.createRunInstancesInput(spotInstanceType.instanceType, bidPrice)
resp, err := i.region.services.ec2.RunInstances(runInstancesInput)

if err != nil {
Expand All @@ -349,18 +348,20 @@ func (i *instance) launchSpotReplacement() error {
return nil
}

func (i *instance) getPricetoBid(
baseOnDemandPrice float64, currentSpotPrice float64) float64 {
func (i *instance) getPricetoBid(currentSpotPrice float64) float64 {

logger.Println("BiddingPolicy: ", i.region.conf.BiddingPolicy)

if i.region.conf.BiddingPolicy == DefaultBiddingPolicy {
logger.Println("Launching spot instance with a bid =", baseOnDemandPrice)
return baseOnDemandPrice
logger.Println("Launching spot instance with bid price", i.price,
"set to the price of the original on-demand instances")
return i.price
}

bufferPrice := math.Min(baseOnDemandPrice, currentSpotPrice*(1.0+i.region.conf.SpotPriceBufferPercentage/100.0))
logger.Println("Launching spot instance with a bid =", bufferPrice)
p := i.region.conf.SpotPriceBufferPercentage
bufferPrice := math.Min(i.price, currentSpotPrice*(1.0+p/100.0))
logger.Printf("Launching spot instance with bid price %.5f set based on "+
"the current spot price %.5f with an additional buffer of %.2f%%\n",
bufferPrice, currentSpotPrice, p)
return bufferPrice
}

Expand Down
9 changes: 4 additions & 5 deletions core/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1107,14 +1107,13 @@ func TestGetPricetoBid(t *testing.T) {
name: "us-east-1",
conf: cfg,
},
price: tt.currentOnDemandPrice,
}

currentSpotPrice := tt.currentSpotPrice
currentOnDemandPrice := tt.currentOnDemandPrice
actualPrice := i.getPricetoBid(currentOnDemandPrice, currentSpotPrice)
actualPrice := i.getPricetoBid(tt.currentSpotPrice)
if math.Abs(actualPrice-tt.want) > 0.000001 {
t.Errorf("percentage = %.2f, policy = %s, expected price = %.5f, want %.5f, currentSpotPrice = %.5f",
tt.spotPercentage, tt.policy, actualPrice, tt.want, currentSpotPrice)
t.Errorf("percentage = %.2f, policy = %s, bid_price = %.5f, expected_price %.5f, currentSpotPrice = %.5f",
tt.spotPercentage, tt.policy, actualPrice, tt.want, tt.currentSpotPrice)
}
}
}
Expand Down
53 changes: 25 additions & 28 deletions core/region.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,31 +206,30 @@ func (r *region) determineInstanceTypeInformation(cfg *Config) {
price.spot = make(spotPriceMap)
price.ebsSurcharge = it.Pricing[r.name].EBSSurcharge

// if at this point the instance price is still zero, then that
// particular instance type doesn't even exist in the current
// region, so we don't even need to create an empty spot pricing
// data structure for it
if price.onDemand > 0 {
// for each instance type populate the HW spec information
info = instanceTypeInformation{
instanceType: it.InstanceType,
vCPU: it.VCPU,
memory: it.Memory,
GPU: it.GPU,
pricing: price,
virtualizationTypes: it.LinuxVirtualizationTypes,
hasEBSOptimization: it.EBSOptimized,
}
if price.onDemand == 0 {
logger.Printf("Missing on-demand price information for %s, "+
"we won't be able to replace on-demand nodes of this instance type\n",
it.InstanceType)
}

if it.Storage != nil {
info.hasInstanceStore = true
info.instanceStoreDeviceSize = it.Storage.Size
info.instanceStoreDeviceCount = it.Storage.Devices
info.instanceStoreIsSSD = it.Storage.SSD
}
debug.Println(info)
r.instanceTypeInformation[it.InstanceType] = info
info = instanceTypeInformation{
instanceType: it.InstanceType,
vCPU: it.VCPU,
memory: it.Memory,
GPU: it.GPU,
pricing: price,
virtualizationTypes: it.LinuxVirtualizationTypes,
hasEBSOptimization: it.EBSOptimized,
}

if it.Storage != nil {
info.hasInstanceStore = true
info.instanceStoreDeviceSize = it.Storage.Size
info.instanceStoreDeviceCount = it.Storage.Devices
info.instanceStoreIsSSD = it.Storage.SSD
}
debug.Println(info)
r.instanceTypeInformation[it.InstanceType] = info
}
// this is safe to do once outside of the loop because the call will only
// return entries about the available instance types, so no invalid instance
Expand Down Expand Up @@ -261,18 +260,16 @@ func (r *region) requestSpotPrices() error {

instType, az := *priceInfo.InstanceType, *priceInfo.AvailabilityZone

// failure to parse this means that the instance is not available on the
// spot market
price, err := strconv.ParseFloat(*priceInfo.SpotPrice, 64)
if err != nil {
logger.Println(r.name, "Instance type ", instType,
"is not available on the spot market")
"Coudln't parse spot price, may not be available on the spot market")
continue
}

if r.instanceTypeInformation[instType].pricing.spot == nil {
logger.Println(r.name, "Instance data missing for", instType, "in", az,
"skipping because this region is currently not supported")
logger.Println(r.name, "Spot pricing data missing for", instType, "in", az,
"this instance type may not available on the spot market here")
continue
}

Expand Down

0 comments on commit d343dfb

Please sign in to comment.