Skip to content

Commit e88c992

Browse files
committed
Support launching spot instances when missing on-demand pricing data
1 parent 45f3399 commit e88c992

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
@@ -266,8 +266,8 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
266266

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

269-
logger.Println("Comparing ", candidate.instanceType, " with ",
270-
current.instanceType)
269+
logger.Printf("Comparing %s with %s ",
270+
current.instanceType, candidate.instanceType)
271271

272272
candidatePrice := i.calculatePrice(candidate)
273273

@@ -280,7 +280,7 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
280280
bestPrice = candidatePrice
281281
chosenSpotType = candidate.instanceType
282282
cheapest = candidate
283-
debug.Println("Best option is now: ", chosenSpotType, " at ", bestPrice)
283+
logger.Println("Found compatible instance type: ", chosenSpotType, " at ", bestPrice)
284284
} else if chosenSpotType != "" {
285285
debug.Println("Current best option: ", chosenSpotType, " at ", bestPrice)
286286
}
@@ -293,7 +293,7 @@ func (i *instance) getCheapestCompatibleSpotInstanceType(allowedList []string, d
293293
}
294294

295295
func (i *instance) launchSpotReplacement() error {
296-
instanceType, err := i.getCheapestCompatibleSpotInstanceType(
296+
spotInstanceType, err := i.getCheapestCompatibleSpotInstanceType(
297297
i.asg.getAllowedInstanceTypes(i),
298298
i.asg.getDisallowedInstanceTypes(i))
299299

@@ -302,10 +302,9 @@ func (i *instance) launchSpotReplacement() error {
302302
return err
303303
}
304304

305-
bidPrice := i.getPricetoBid(instanceType.pricing.onDemand,
306-
instanceType.pricing.spot[*i.Placement.AvailabilityZone])
305+
bidPrice := i.getPricetoBid(spotInstanceType.pricing.spot[*i.Placement.AvailabilityZone])
307306

308-
runInstancesInput := i.createRunInstancesInput(instanceType.instanceType, bidPrice)
307+
runInstancesInput := i.createRunInstancesInput(spotInstanceType.instanceType, bidPrice)
309308
resp, err := i.region.services.ec2.RunInstances(runInstancesInput)
310309

311310
if err != nil {
@@ -322,18 +321,20 @@ func (i *instance) launchSpotReplacement() error {
322321
return nil
323322
}
324323

325-
func (i *instance) getPricetoBid(
326-
baseOnDemandPrice float64, currentSpotPrice float64) float64 {
324+
func (i *instance) getPricetoBid(currentSpotPrice float64) float64 {
327325

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

330328
if i.region.conf.BiddingPolicy == DefaultBiddingPolicy {
331-
logger.Println("Launching spot instance with a bid =", baseOnDemandPrice)
332-
return baseOnDemandPrice
329+
logger.Println("Launching spot instance with bid price", i.price,
330+
"set to the price of the original on-demand instances")
331+
return i.price
333332
}
334-
335-
bufferPrice := math.Min(baseOnDemandPrice, currentSpotPrice*(1.0+i.region.conf.SpotPriceBufferPercentage/100.0))
336-
logger.Println("Launching spot instance with a bid =", bufferPrice)
333+
p := i.region.conf.SpotPriceBufferPercentage
334+
bufferPrice := math.Min(i.price, currentSpotPrice*(1.0+p/100.0))
335+
logger.Printf("Launching spot instance with bid price %.5f set based on "+
336+
"the current spot price %.5f with an additional buffer of %.2f%%\n",
337+
bufferPrice, currentSpotPrice, p)
337338
return bufferPrice
338339
}
339340

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
@@ -207,31 +207,30 @@ func (r *region) determineInstanceTypeInformation(cfg *Config) {
207207
price.spot = make(spotPriceMap)
208208
price.ebsSurcharge = it.Pricing[r.name].EBSSurcharge
209209

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

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

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

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

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

0 commit comments

Comments
 (0)