Skip to content

Commit d343dfb

Browse files
committed
Support launching spot instances when missing on-demand pricing data
1 parent cd63b76 commit d343dfb

File tree

3 files changed

+44
-47
lines changed

3 files changed

+44
-47
lines changed

core/instance.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
293293

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

296-
logger.Println("Comparing ", candidate.instanceType, " with ",
297-
current.instanceType)
296+
logger.Printf("Comparing %s with %s ",
297+
current.instanceType, candidate.instanceType)
298298

299299
candidatePrice := i.calculatePrice(candidate)
300300

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

322322
func (i *instance) launchSpotReplacement() error {
323-
instanceType, err := i.getCheapestCompatibleSpotInstanceType(
323+
spotInstanceType, err := i.getCheapestCompatibleSpotInstanceType(
324324
i.asg.getAllowedInstanceTypes(i),
325325
i.asg.getDisallowedInstanceTypes(i))
326326

@@ -329,10 +329,9 @@ func (i *instance) launchSpotReplacement() error {
329329
return err
330330
}
331331

332-
bidPrice := i.getPricetoBid(instanceType.pricing.onDemand,
333-
instanceType.pricing.spot[*i.Placement.AvailabilityZone])
332+
bidPrice := i.getPricetoBid(spotInstanceType.pricing.spot[*i.Placement.AvailabilityZone])
334333

335-
runInstancesInput := i.createRunInstancesInput(instanceType.instanceType, bidPrice)
334+
runInstancesInput := i.createRunInstancesInput(spotInstanceType.instanceType, bidPrice)
336335
resp, err := i.region.services.ec2.RunInstances(runInstancesInput)
337336

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

352-
func (i *instance) getPricetoBid(
353-
baseOnDemandPrice float64, currentSpotPrice float64) float64 {
351+
func (i *instance) getPricetoBid(currentSpotPrice float64) float64 {
354352

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

357355
if i.region.conf.BiddingPolicy == DefaultBiddingPolicy {
358-
logger.Println("Launching spot instance with a bid =", baseOnDemandPrice)
359-
return baseOnDemandPrice
356+
logger.Println("Launching spot instance with bid price", i.price,
357+
"set to the price of the original on-demand instances")
358+
return i.price
360359
}
361-
362-
bufferPrice := math.Min(baseOnDemandPrice, currentSpotPrice*(1.0+i.region.conf.SpotPriceBufferPercentage/100.0))
363-
logger.Println("Launching spot instance with a bid =", bufferPrice)
360+
p := i.region.conf.SpotPriceBufferPercentage
361+
bufferPrice := math.Min(i.price, currentSpotPrice*(1.0+p/100.0))
362+
logger.Printf("Launching spot instance with bid price %.5f set based on "+
363+
"the current spot price %.5f with an additional buffer of %.2f%%\n",
364+
bufferPrice, currentSpotPrice, p)
364365
return bufferPrice
365366
}
366367

core/instance_test.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,14 +1107,13 @@ func TestGetPricetoBid(t *testing.T) {
11071107
name: "us-east-1",
11081108
conf: cfg,
11091109
},
1110+
price: tt.currentOnDemandPrice,
11101111
}
11111112

1112-
currentSpotPrice := tt.currentSpotPrice
1113-
currentOnDemandPrice := tt.currentOnDemandPrice
1114-
actualPrice := i.getPricetoBid(currentOnDemandPrice, currentSpotPrice)
1113+
actualPrice := i.getPricetoBid(tt.currentSpotPrice)
11151114
if math.Abs(actualPrice-tt.want) > 0.000001 {
1116-
t.Errorf("percentage = %.2f, policy = %s, expected price = %.5f, want %.5f, currentSpotPrice = %.5f",
1117-
tt.spotPercentage, tt.policy, actualPrice, tt.want, currentSpotPrice)
1115+
t.Errorf("percentage = %.2f, policy = %s, bid_price = %.5f, expected_price %.5f, currentSpotPrice = %.5f",
1116+
tt.spotPercentage, tt.policy, actualPrice, tt.want, tt.currentSpotPrice)
11181117
}
11191118
}
11201119
}

core/region.go

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -206,31 +206,30 @@ func (r *region) determineInstanceTypeInformation(cfg *Config) {
206206
price.spot = make(spotPriceMap)
207207
price.ebsSurcharge = it.Pricing[r.name].EBSSurcharge
208208

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

225-
if it.Storage != nil {
226-
info.hasInstanceStore = true
227-
info.instanceStoreDeviceSize = it.Storage.Size
228-
info.instanceStoreDeviceCount = it.Storage.Devices
229-
info.instanceStoreIsSSD = it.Storage.SSD
230-
}
231-
debug.Println(info)
232-
r.instanceTypeInformation[it.InstanceType] = info
215+
info = instanceTypeInformation{
216+
instanceType: it.InstanceType,
217+
vCPU: it.VCPU,
218+
memory: it.Memory,
219+
GPU: it.GPU,
220+
pricing: price,
221+
virtualizationTypes: it.LinuxVirtualizationTypes,
222+
hasEBSOptimization: it.EBSOptimized,
223+
}
224+
225+
if it.Storage != nil {
226+
info.hasInstanceStore = true
227+
info.instanceStoreDeviceSize = it.Storage.Size
228+
info.instanceStoreDeviceCount = it.Storage.Devices
229+
info.instanceStoreIsSSD = it.Storage.SSD
233230
}
231+
debug.Println(info)
232+
r.instanceTypeInformation[it.InstanceType] = info
234233
}
235234
// this is safe to do once outside of the loop because the call will only
236235
// return entries about the available instance types, so no invalid instance
@@ -261,18 +260,16 @@ func (r *region) requestSpotPrices() error {
261260

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

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

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

0 commit comments

Comments
 (0)