}
}
-func (ai *azureInstance) PriceHistory() []cloud.InstancePrice {
+func (ai *azureInstance) PriceHistory(arvados.InstanceType) []cloud.InstancePrice {
return nil
}
SubnetID string
AdminUsername string
EBSVolumeType string
+ EBSPrice float64
IAMInstanceProfile string
SpotPriceUpdateInterval arvados.Duration
}
// Spot price that is in effect when your Spot Instance is running."
// (The use of the phrase "is running", as opposed to "was launched",
// hints that pricing is dynamic.)
-func (inst *ec2Instance) PriceHistory() []cloud.InstancePrice {
+func (inst *ec2Instance) PriceHistory(instType arvados.InstanceType) []cloud.InstancePrice {
inst.provider.pricesLock.Lock()
defer inst.provider.pricesLock.Unlock()
pk := priceKey{
spot: aws.StringValue(inst.instance.InstanceLifecycle) == "spot",
availabilityZone: inst.availabilityZone,
}
- return inst.provider.prices[pk]
+ var prices []cloud.InstancePrice
+ for _, price := range inst.provider.prices[pk] {
+ // ceil(added scratch space in GiB)
+ gib := (instType.AddedScratch + 1<<30 - 1) >> 30
+ monthly := inst.provider.ec2config.EBSPrice * float64(gib)
+ hourly := monthly / 30 / 24
+ price.Price += hourly
+ prices = append(prices, price)
+ }
+ return prices
}
type rateLimitError struct {
return nil, nil
}
-func GetInstanceSet(c *check.C) (cloud.InstanceSet, cloud.ImageID, arvados.Cluster) {
+func GetInstanceSet(c *check.C) (*ec2InstanceSet, cloud.ImageID, arvados.Cluster) {
cluster := arvados.Cluster{
InstanceTypes: arvados.InstanceTypeMap(map[string]arvados.InstanceType{
"tiny": {
ap, err := newEC2InstanceSet(exampleCfg.DriverParameters, "test123", nil, logrus.StandardLogger())
c.Assert(err, check.IsNil)
- return ap, cloud.ImageID(exampleCfg.ImageIDForTestSuite), cluster
+ return ap.(*ec2InstanceSet), cloud.ImageID(exampleCfg.ImageIDForTestSuite), cluster
}
ap := ec2InstanceSet{
ec2config: ec2InstanceSetConfig{},
}
}()
+ ap.ec2config.EBSPrice = 0.1 // $/GiB/month
inst1, err := ap.Create(cluster.InstanceTypes["tiny-preemptible"], img, tags, "true", pk)
c.Assert(err, check.IsNil)
defer inst1.Destroy()
}
for _, inst := range instances {
- hist := inst.PriceHistory()
+ hist := inst.PriceHistory(arvados.InstanceType{})
c.Logf("%s price history: %v", inst.ID(), hist)
c.Check(len(hist) > 0, check.Equals, true)
+
+ histWithScratch := inst.PriceHistory(arvados.InstanceType{AddedScratch: 640 << 30})
+ c.Logf("%s price history with 640 GiB scratch: %v", inst.ID(), histWithScratch)
+
for i, ip := range hist {
c.Check(ip.Price, check.Not(check.Equals), 0.0)
if i > 0 {
c.Check(ip.StartTime.Before(hist[i-1].StartTime), check.Equals, true)
}
+ c.Check(ip.Price < histWithScratch[i].Price, check.Equals, true)
}
}
}
// Replace tags with the given tags
SetTags(InstanceTags) error
- // Get recent price history, if available
- PriceHistory() []InstancePrice
+ // Get recent price history, if available. The InstanceType is
+ // supplied as an argument so the driver implementation can
+ // account for AddedScratch cost without requesting the volume
+ // attachment information from the provider's API.
+ PriceHistory(arvados.InstanceType) []InstancePrice
// Shut down the node
Destroy() error
sshService test.SSHService
}
-func (i *instance) ID() cloud.InstanceID { return cloud.InstanceID(i.instanceType.ProviderType) }
-func (i *instance) String() string { return i.instanceType.ProviderType }
-func (i *instance) ProviderType() string { return i.instanceType.ProviderType }
-func (i *instance) Address() string { return i.sshService.Address() }
-func (i *instance) PriceHistory() []cloud.InstancePrice { return nil }
-func (i *instance) RemoteUser() string { return i.adminUser }
-func (i *instance) Tags() cloud.InstanceTags { return i.tags }
+func (i *instance) ID() cloud.InstanceID { return cloud.InstanceID(i.instanceType.ProviderType) }
+func (i *instance) String() string { return i.instanceType.ProviderType }
+func (i *instance) ProviderType() string { return i.instanceType.ProviderType }
+func (i *instance) Address() string { return i.sshService.Address() }
+func (i *instance) PriceHistory(arvados.InstanceType) []cloud.InstancePrice { return nil }
+func (i *instance) RemoteUser() string { return i.adminUser }
+func (i *instance) Tags() cloud.InstanceTags { return i.tags }
func (i *instance) SetTags(tags cloud.InstanceTags) error {
i.tags = tags
return nil
# calculating container cost estimates.
SpotPriceUpdateInterval: 24h
+ # (ec2) per-GiB-month cost of EBS volumes. Matches
+ # EBSVolumeType. Used to account for AddedScratch when
+ # calculating container cost estimates. Note that
+ # https://aws.amazon.com/ebs/pricing/ defines GB to mean
+ # GiB, so an advertised price $0.10/GB indicates a real
+ # price of $0.10/GiB and can be entered here as 0.10.
+ EBSPrice: 0.10
+
# (azure) Credentials.
SubscriptionID: ""
ClientID: ""
RAM: 128MiB
IncludedScratch: 16GB
AddedScratch: 0
+ # Hourly price ($), used to select node types for containers,
+ # and to calculate estimated container costs. For spot
+ # instances on EC2, this is also used as the maximum price
+ # when launching spot instances, while the estimated container
+ # cost is computed based on the current spot price according
+ # to AWS. On Azure, and on-demand instances on EC2, the price
+ # given here is used to compute container cost estimates.
Price: 0.1
Preemptible: false
# Include this section if the node type includes GPU (CUDA) support
spanEnd := now
for _, ip := range prices {
spanStart := ip.StartTime
+ if spanStart.After(now) {
+ // pricing information from the future -- not
+ // expected from AWS, but possible in
+ // principle, and exercised by tests.
+ continue
+ }
last := false
if spanStart.Before(cr.costStartTime) {
spanStart = cr.costStartTime
}
spanEnd = spanStart
}
+
return cost
}
cost = cr.calculateCost(now)
c.Check(cost, Equals, 1.0/2+2.0/4+3.0/4)
+ cost = cr.calculateCost(now.Add(-time.Hour / 2))
+ c.Check(cost, Equals, 0.5)
+
c.Logf("%s", logbuf.String())
c.Check(logbuf.String(), Matches, `(?ms).*Instance price changed to 1\.00 at 20.* changed to 2\.00 .* changed to 3\.00 .*`)
c.Check(logbuf.String(), Not(Matches), `(?ms).*changed to 2\.00 .* changed to 2\.00 .*`)
return dst
}
-func (si stubInstance) PriceHistory() []cloud.InstancePrice {
+func (si stubInstance) PriceHistory(arvados.InstanceType) []cloud.InstancePrice {
return nil
}
}
before := time.Now()
var stdin io.Reader
- if prices := wkr.instance.PriceHistory(); len(prices) > 0 {
+ if prices := wkr.instance.PriceHistory(wkr.instType); len(prices) > 0 {
j, _ := json.Marshal(prices)
stdin = bytes.NewReader(j)
}