Skip to content
This repository was archived by the owner on Feb 11, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ EC2 and VPC.
* Define region-specific configurations so Vagrant can manage machines
in multiple regions.
* Package running instances into new vagrant-aws friendly boxes
* Spot Instance Support

## Usage

Expand Down Expand Up @@ -153,6 +154,10 @@ This provider exposes quite a few provider-specific configuration options:
* `unregister_elb_from_az` - Removes the ELB from the AZ on removal of the last instance if true (default). In non default VPC this has to be false.
* `terminate_on_shutdown` - Indicates whether an instance stops or terminates
when you initiate shutdown from the instance.

* `spot_instance` - Boolean value; indicates whether the config is for a spot instance, or on-demand. For more information about spot instances, see the [AWS Documentation](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-spot-instances-work.html)
* `max_spot_price` - Decimal value; state the maximum bid for your spot instance. Ignored if `spot_instance` is not true.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be spot_max_price.

* `spot_valid_until` - Timestamp; when this spot instance request should expire, destroying any related instances. Ignored if `spot_instance` is not true.

These can be set like typical provider-specific configuration:

Expand Down
80 changes: 79 additions & 1 deletion lib/vagrant-aws/action/run_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,12 @@ def call(env)
end

begin
server = env[:aws_compute].servers.create(options)
server = if region_config.spot_instance
server_from_spot_request(env, region_config)
else
env[:aws_compute].servers.create(options)
end
raise Errors::FogError, :message => "server is nil" unless server
rescue Fog::Compute::AWS::NotFound => e
# Invalid subnet doesn't have its own error so we catch and
# check the error message here.
Expand All @@ -129,6 +134,10 @@ def call(env)
# Immediately save the ID since it is created at this point.
env[:machine].id = server.id

# Spot Instances don't support tagging arguments on creation
# Retrospectively tag the server to handle this
env[:aws_compute].create_tags(server.id,tags)

# Wait for the instance to be ready first
env[:metrics]["instance_ready_time"] = Util::Timer.time do
tries = region_config.instance_ready_timeout / 2
Expand Down Expand Up @@ -213,6 +222,75 @@ def call(env)
@app.call(env)
end

# returns a fog server or nil
def server_from_spot_request(env, config)
# prepare request args
options = {
'InstanceCount' => 1,
'LaunchSpecification.KeyName' => config.keypair_name,
'LaunchSpecification.Placement.AvailabilityZone' => config.availability_zone,
'LaunchSpecification.UserData' => config.user_data,
'LaunchSpecification.SubnetId' => config.subnet_id,
'LaunchSpecification.BlockDeviceMapping' => config.block_device_mapping,
'ValidUntil' => config.spot_valid_until
}
security_group_key = config.subnet_id.nil? ? 'LaunchSpecification.SecurityGroup' : 'LaunchSpecification.SecurityGroupId'
options[security_group_key] = config.security_groups
options.delete_if { |key, value| value.nil? }

env[:ui].info(I18n.t("vagrant_aws.launching_spot_instance"))
env[:ui].info(" -- Price: #{config.spot_max_price}")
env[:ui].info(" -- Valid until: #{config.spot_valid_until}") if config.spot_valid_until
env[:ui].info(" -- Monitoring: #{config.monitoring}") if config.monitoring

# create the spot instance
spot_req = env[:aws_compute].request_spot_instances(
config.ami,
config.instance_type,
config.spot_max_price,
options).body["spotInstanceRequestSet"].first

spot_request_id = spot_req["spotInstanceRequestId"]
@logger.info("Spot request ID: #{spot_request_id}")

# initialize state
status_code = ""
while true
sleep 5 # TODO make it a param

raise Errors::FogError, :message => "Interrupted" if env[:interrupted]
spot_req = env[:aws_compute].describe_spot_instance_requests(
'spot-instance-request-id' => [spot_request_id]).body["spotInstanceRequestSet"].first

# waiting for spot request ready
next unless spot_req

# display something whenever the status code changes
if status_code != spot_req["state"]
env[:ui].info(spot_req["fault"]["message"])
status_code = spot_req["state"]
end
spot_state = spot_req["state"].to_sym
case spot_state
when :not_created, :open
@logger.debug("Spot request #{spot_state} #{status_code}, waiting")
when :active
break; # :)
when :closed, :cancelled, :failed
msg = "Spot request #{spot_state} #{status_code}, aborting"
@logger.error(msg)
raise Errors::FogError, :message => msg
else
@logger.debug("Unknown spot state #{spot_state} #{status_code}, waiting")
end
end
# cancel the spot request but let the server go thru
env[:aws_compute].cancel_spot_instance_requests(spot_request_id)
server = env[:aws_compute].servers.get(spot_req["instanceId"])
env[:aws_compute].create_tags(server.identity, config.tags)
server
end

def recover(env)
return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)

Expand Down
28 changes: 28 additions & 0 deletions lib/vagrant-aws/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ class Config < Vagrant.plugin("2", :config)
# @return [Array<Hash>]
attr_accessor :block_device_mapping

# Launch as spot instance
#
# @return [Boolean]
attr_accessor :spot_instance

# Spot request max price
#
# @return [String]
attr_accessor :spot_max_price

# Spot request validity
#
# @return [Time]
attr_accessor :spot_valid_until

# Indicates whether an instance stops or terminates when you initiate shutdown from the instance
#
# @return [bool]
Expand Down Expand Up @@ -207,6 +222,9 @@ def initialize(region_specific=false)
@user_data = UNSET_VALUE
@use_iam_profile = UNSET_VALUE
@block_device_mapping = []
@spot_instance = UNSET_VALUE
@spot_max_price = UNSET_VALUE
@spot_valid_until = UNSET_VALUE
@elastic_ip = UNSET_VALUE
@iam_instance_profile_arn = UNSET_VALUE
@iam_instance_profile_name = UNSET_VALUE
Expand Down Expand Up @@ -357,6 +375,15 @@ def finalize!
# User Data is nil by default
@user_data = nil if @user_data == UNSET_VALUE

# By default don't use spot requests
@spot_instance = false if @spot_instance == UNSET_VALUE

# Required, no default
@spot_max_price = nil if @spot_max_price == UNSET_VALUE

# Default: Request is effective indefinitely.
@spot_valid_until = nil if @spot_valid_until == UNSET_VALUE

# default false
@terminate_on_shutdown = false if @terminate_on_shutdown == UNSET_VALUE

Expand Down Expand Up @@ -433,6 +460,7 @@ def validate(machine)
end

errors << I18n.t("vagrant_aws.config.ami_required", :region => @region) if config.ami.nil?
errors << I18n.t("vagrant_aws.config.spot_price_required") if config.spot_instance && config.spot_max_price.nil?
end

{ "AWS Provider" => errors }
Expand Down
2 changes: 1 addition & 1 deletion lib/vagrant-aws/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module VagrantPlugins
module AWS
VERSION = '0.6.1'
VERSION = '0.6.2.spot'
end
end
4 changes: 4 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ en:

launching_instance: |-
Launching an instance with the following settings...
launching_spot_instance: |-
Launching a spot request instance with the following settings...
launch_no_keypair: |-
Warning! You didn't specify a keypair to launch your instance with.
This can sometimes result in not being able to access your instance.
Expand Down Expand Up @@ -68,6 +70,8 @@ en:
An access key ID must be specified via "access_key_id"
ami_required: |-
An AMI must be configured via "ami" (region: #{region})
spot_price_required: |-
Spot request is missing "spot_max_price"
private_key_missing: |-
The specified private key for AWS could not be found
region_required: |-
Expand Down
3 changes: 3 additions & 0 deletions spec/vagrant-aws/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
its("user_data") { should be_nil }
its("use_iam_profile") { should be false }
its("block_device_mapping") {should == [] }
its("spot_instance") { should be_false }
its("spot_max_price") { should be_nil }
its("spot_valid_until") { should be_nil }
its("elastic_ip") { should be_nil }
its("terminate_on_shutdown") { should == false }
its("ssh_host_attribute") { should be_nil }
Expand Down