Exemple #1
0
// CostForDuration returns the cost of running a host between the given start and end times
func (cloudManager *EC2Manager) CostForDuration(h *host.Host, start, end time.Time) (float64, error) {
	// sanity check
	if end.Before(start) || util.IsZeroTime(start) || util.IsZeroTime(end) {
		return 0, fmt.Errorf("task timing data is malformed")
	}
	// grab instance details from EC2
	ec2Handle := getUSEast(*cloudManager.awsCredentials)
	instance, err := getInstanceInfo(ec2Handle, h.Id)
	if err != nil {
		return 0, err
	}
	os := osLinux
	if strings.Contains(h.Distro.Arch, "windows") {
		os = osWindows
	}
	dur := end.Sub(start)
	region := azToRegion(instance.AvailabilityZone)
	iType := instance.InstanceType

	ebsCost, err := blockDeviceCosts(ec2Handle, instance.BlockDevices, dur)
	if err != nil {
		return 0, fmt.Errorf("calculating block device costs: %v", err)
	}
	hostCost, err := onDemandCost(&pkgOnDemandPriceFetcher, os, iType, region, dur)
	if err != nil {
		return 0, err
	}
	return hostCost + ebsCost, nil
}
Exemple #2
0
// CostForDuration computes the currency amount it costs to use the given host between a start and end time.
// The Spot prices estimation takes both spot prices and EBS prices into account. Here's a breakdown:
//
// Spot prices are determined by a fluctuating price market. We set a bid price and get a host if the
// "market" price is lower than that. We are billed by what the current spot price is, and then charged
// the current spot price once our hour billing cycle is up, and so on. This calculator ONLY returns
// the cost of the time used between the start and end times, it does not account for unused host time.
//
// EBS volumes are charged on a per-gigabyte-per-month rate for usage, rounded to the nearest hour.
// There is no EBS price API, so we scrape it from Amazon's UI. This could unexpectedly break in the
// future, but, so far, the JSON we are loading hasn't changed format in half a decade. EBS spending
// for a single task ends up being virtually nothing compared to the machine price, but those fractions
// of cents will add up over time.
//
// CostForDuration returns the total cost and any errors that occur.
func (cloudManager *EC2SpotManager) CostForDuration(h *host.Host, start, end time.Time) (float64, error) {
	// sanity check
	if end.Before(start) || util.IsZeroTime(start) || util.IsZeroTime(end) {
		return 0, fmt.Errorf("task timing data is malformed")
	}

	// grab instance details from EC2
	spotDetails, err := cloudManager.describeSpotRequest(h.Id)
	if err != nil {
		return 0, err
	}
	ec2Handle := getUSEast(*cloudManager.awsCredentials)
	instance, err := getInstanceInfo(ec2Handle, spotDetails.InstanceId)
	if err != nil {
		return 0, err
	}
	os := osLinux
	if strings.Contains(h.Distro.Arch, "windows") {
		os = osWindows
	}
	ebsCost, err := blockDeviceCosts(ec2Handle, instance.BlockDevices, end.Sub(start))
	if err != nil {
		return 0, fmt.Errorf("calculating block device costs: %v", err)
	}
	spotCost, err := cloudManager.calculateSpotCost(instance, os, start, end)
	if err != nil {
		return 0, err
	}
	return spotCost + ebsCost, nil
}
func TestbucketResource(t *testing.T) {
	Convey("With a start time and a bucket size of 10 and 10 buckets", t, func() {
		frameStart := time.Now()
		// 10 buckets * 10 bucket size = 100
		frameEnd := frameStart.Add(time.Duration(100))
		bucketSize := time.Duration(10)
		Convey("when resource start time is equal to end time should error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameEnd
			resourceEnd := frameEnd.Add(time.Duration(10))
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when resource start time is greater than end time should error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameEnd.Add(time.Duration(10))
			resourceEnd := frameEnd.Add(time.Duration(20))
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when resource end time is equal to start time should error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameStart.Add(time.Duration(-10))
			resourceEnd := frameStart
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when resource end time is less than start time should error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameStart.Add(time.Duration(-30))
			resourceEnd := frameStart.Add(time.Duration(-10))
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when resource end time is less than resource start time, should error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameStart.Add(time.Duration(10))
			resourceEnd := frameStart
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when resource start is zero, errors out", func() {
			buckets := make([]Bucket, 10)
			resourceStart := time.Time{}
			resourceEnd := frameStart.Add(time.Duration(1))
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldNotBeNil)
		})
		Convey("when the resource start and end time are in the same bucket, only one bucket has the difference", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameStart.Add(time.Duration(1))
			resourceEnd := frameStart.Add(time.Duration(5))
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldBeNil)
			So(buckets[0].TotalTime, ShouldEqual, time.Duration(4))
			for i := 1; i < 10; i++ {
				So(buckets[i].TotalTime, ShouldEqual, 0)
			}
		})
		Convey("when the resourceEnd is zero, there is no error", func() {
			buckets := make([]Bucket, 10)
			resourceStart := frameStart.Add(time.Duration(10))
			resourceEnd := util.ZeroTime
			So(util.IsZeroTime(resourceEnd), ShouldBeTrue)
			resource := ResourceInfo{
				Start: resourceStart,
				End:   resourceEnd,
			}
			buckets, err := bucketResource(resource, frameStart, frameEnd, bucketSize, buckets)
			So(err, ShouldBeNil)
			So(buckets[0].TotalTime, ShouldEqual, 0)
			for i := 1; i < 10; i++ {
				So(buckets[i].TotalTime, ShouldEqual, 10)
			}
		})

	})
}
// bucketResource buckets amounts of time based on a number of buckets and the size of them.
// Given a resource with a start and end time, where the end >= start,
// a time frame with a frameStart and frameEnd, where frameEnd >= frameStart,
// a bucketSize that represents the amount of time each bucket holds
// and a list of buckets that may already have time in them,
// BucketResource will split the time and add the time the corresponds to a given buck to that bucket.
func bucketResource(resource ResourceInfo, frameStart, frameEnd time.Time, bucketSize time.Duration,
	currentBuckets []Bucket) ([]Bucket, error) {

	start := resource.Start
	end := resource.End
	// double check so that there are no panics
	if start.After(frameEnd) || start.Equal(frameEnd) {
		return currentBuckets, fmt.Errorf("invalid resource start time %v that is after the time frame %v", start, frameEnd)
	}

	if util.IsZeroTime(start) {
		return currentBuckets, fmt.Errorf("start time is zero")
	}

	if !util.IsZeroTime(end) && (end.Before(frameStart) || end.Equal(frameStart)) {
		return currentBuckets, fmt.Errorf("invalid resource end time, %v that is before the time frame, %v", end, frameStart)
	}

	if !util.IsZeroTime(end) && end.Before(start) {
		return currentBuckets, fmt.Errorf("termination time, %v is before start time, %v and exists", end, start)
	}

	// if the times are equal then just return since nothing should be bucketed
	if end.Equal(start) {
		return currentBuckets, nil
	}

	// If the resource starts before the beginning of the frame,
	// the startBucket is the first one. The startOffset is the offset
	// of time from the beginning of the start bucket, so that is  0.
	startOffset := time.Duration(0)
	startBucket := time.Duration(0)
	if start.After(frameStart) {
		startOffset = start.Sub(frameStart)
		startBucket = startOffset / bucketSize
	}
	// If the resource ends after the end of the frame, the end bucket is the last bucket
	// the end offset is the entirety of that bucket.
	endBucket := time.Duration(len(currentBuckets) - 1)
	endOffset := bucketSize * (endBucket + 1)

	if !(util.IsZeroTime(end) || end.After(frameEnd) || end.Equal(frameEnd)) {
		endOffset = end.Sub(frameStart)
		endBucket = endOffset / bucketSize
	}

	// If the startBucket and the endBucket are the same, that means there is only one bucket.
	// The amount that goes in that bucket is the difference in the resources start time and end time.
	if startBucket == endBucket {
		currentBuckets[startBucket] = addBucketTime(endOffset-startOffset, resource, currentBuckets[startBucket])
		return currentBuckets, nil

	} else {
		// add the difference between the startOffset and the amount of time that has passed in the start and end bucket
		// to the start and end buckets.
		currentBuckets[startBucket] = addBucketTime((startBucket+1)*bucketSize-startOffset, resource, currentBuckets[startBucket])
		currentBuckets[endBucket] = addBucketTime(endOffset-endBucket*bucketSize, resource, currentBuckets[endBucket])
	}
	for i := startBucket + 1; i < endBucket; i++ {
		currentBuckets[i] = addBucketTime(bucketSize, resource, currentBuckets[i])
	}
	return currentBuckets, nil
}