From e88c99286bdc1fde52bf46a2248e8792a35df598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20M=C4=83gheru=C8=99an-Stanciu?= Date: Fri, 7 Sep 2018 17:14:30 +0200 Subject: [PATCH] Support launching spot instances when missing on-demand pricing data --- core/instance.go | 29 +++++++++++------------ core/instance_test.go | 9 ++++---- core/region.go | 53 ++++++++++++++++++++----------------------- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/core/instance.go b/core/instance.go index 3e3f7ebc..db35968a 100644 --- a/core/instance.go +++ b/core/instance.go @@ -266,8 +266,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) @@ -280,7 +280,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) } @@ -293,7 +293,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)) @@ -302,10 +302,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 { @@ -322,18 +321,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 } diff --git a/core/instance_test.go b/core/instance_test.go index a6c38594..ccd0b244 100644 --- a/core/instance_test.go +++ b/core/instance_test.go @@ -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) } } } diff --git a/core/region.go b/core/region.go index 19f2063a..8782bd78 100644 --- a/core/region.go +++ b/core/region.go @@ -207,31 +207,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 @@ -262,18 +261,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 }