@ -67,6 +67,13 @@ options:
required: true
required: true
default: null
default: null
aliases: []
aliases: []
spot_price:
version_added: "1.5"
description:
- Maximum spot price to bid, If not set a regular on-demand instance is requested. A spot request is made with this maximum bid. When it is filled, the instance is started.
required: false
default: null
aliases: []
image:
image:
description:
description:
- I(emi) (or I(ami)) to use for the instance
- I(emi) (or I(ami)) to use for the instance
@ -97,6 +104,12 @@ options:
- how long before wait gives up, in seconds
- how long before wait gives up, in seconds
default: 300
default: 300
aliases: []
aliases: []
spot_wait_timeout:
version_added: "1.5"
description:
- how long to wait for the spot instance request to be fulfilled
default: 600
aliases: []
ec2_url:
ec2_url:
description:
description:
- Url to use to connect to EC2 or your Eucalyptus cloud (by default the module will use EC2 endpoints). Must be specified if region is not used. If not set then the value of the EC2_URL environment variable, if any, is used
- Url to use to connect to EC2 or your Eucalyptus cloud (by default the module will use EC2 endpoints). Must be specified if region is not used. If not set then the value of the EC2_URL environment variable, if any, is used
@ -311,6 +324,19 @@ local_action:
vpc_subnet_id: subnet-29e63245
vpc_subnet_id: subnet-29e63245
assign_public_ip: yes
assign_public_ip: yes
# Spot instance example
- local_action:
module: ec2
spot_price: 0.24
spot_wait_timeout: 600
keypair: mykey
group_id: sg-1dc53f72
instance_type: m1.small
image: ami-6e649707
wait: yes
vpc_subnet_id: subnet-29e63245
assign_public_ip: yes
# Launch instances, runs some tasks
# Launch instances, runs some tasks
# and then terminate them
# and then terminate them
@ -619,6 +645,17 @@ def create_block_device(module, ec2, volume):
delete_on_termination=volume.get('delete_on_termination', False),
delete_on_termination=volume.get('delete_on_termination', False),
iops=volume.get('iops'))
iops=volume.get('iops'))
def boto_supports_param_in_spot_request(ec2, param):
"""
Check if Boto library has a <param> in its request_spot_instances() method. For example, the placement_group parameter wasn't added until 2.3.0.
ec2: authenticated ec2 connection object
Returns:
True if boto library has the named param as an argument on the request_spot_instances method, else False
"""
method = getattr(ec2, 'request_spot_instances')
return param in method.func_code.co_varnames
def enforce_count(module, ec2):
def enforce_count(module, ec2):
@ -643,7 +680,6 @@ def enforce_count(module, ec2):
for inst in instance_dict_array:
for inst in instance_dict_array:
instances.append(inst)
instances.append(inst)
elif len(instances) > exact_count:
elif len(instances) > exact_count:
changed = True
changed = True
to_remove = len(instances) - exact_count
to_remove = len(instances) - exact_count
@ -689,6 +725,7 @@ def create_instances(module, ec2, override_count=None):
group_id = module.params.get('group_id')
group_id = module.params.get('group_id')
zone = module.params.get('zone')
zone = module.params.get('zone')
instance_type = module.params.get('instance_type')
instance_type = module.params.get('instance_type')
spot_price = module.params.get('spot_price')
image = module.params.get('image')
image = module.params.get('image')
if override_count:
if override_count:
count = override_count
count = override_count
@ -699,6 +736,7 @@ def create_instances(module, ec2, override_count=None):
ramdisk = module.params.get('ramdisk')
ramdisk = module.params.get('ramdisk')
wait = module.params.get('wait')
wait = module.params.get('wait')
wait_timeout = int(module.params.get('wait_timeout'))
wait_timeout = int(module.params.get('wait_timeout'))
spot_wait_timeout = int(module.params.get('spot_wait_timeout'))
placement_group = module.params.get('placement_group')
placement_group = module.params.get('placement_group')
user_data = module.params.get('user_data')
user_data = module.params.get('user_data')
instance_tags = module.params.get('instance_tags')
instance_tags = module.params.get('instance_tags')
@ -759,16 +797,11 @@ def create_instances(module, ec2, override_count=None):
try:
try:
params = {'image_id': image,
params = {'image_id': image,
'key_name': key_name,
'key_name': key_name,
'client_token': id,
'min_count': count_remaining,
'max_count': count_remaining,
'monitoring_enabled': monitoring,
'monitoring_enabled': monitoring,
'placement': zone,
'placement': zone,
'placement_group': placement_group,
'instance_type': instance_type,
'instance_type': instance_type,
'kernel_id': kernel,
'kernel_id': kernel,
'ramdisk_id': ramdisk,
'ramdisk_id': ramdisk,
'private_ip_address': private_ip,
'user_data': user_data}
'user_data': user_data}
if boto_supports_profile_name_arg(ec2):
if boto_supports_profile_name_arg(ec2):
@ -813,21 +846,63 @@ def create_instances(module, ec2, override_count=None):
params['block_device_map'] = bdm
params['block_device_map'] = bdm
# check to see if we're using spot pricing first before starting instances
if not spot_price:
params.update(dict(
min_count = count_remaining,
max_count = count_remaining,
client_token = id,
placement_group = placement_group,
private_ip_address = private_ip,
))
res = ec2.run_instances(**params)
res = ec2.run_instances(**params)
except boto.exception.BotoServerError, e:
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
instids = [ i.id for i in res.instances ]
instids = [ i.id for i in res.instances ]
while True:
while True:
try:
try:
res.connection.get_all_instances(instids)
ec2 .get_all_instances(instids)
break
break
except boto.exception.EC2ResponseError, e:
except boto.exception.EC2ResponseError as e:
if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
if "<Code>InvalidInstanceID.NotFound</Code>" in str(e):
# there's a race between start and get an instance
# there's a race between start and get an instance
continue
continue
else:
else:
module.fail_json(msg = str(e))
module.fail_json(msg = str(e))
else:
if private_ip:
module.fail_json(
msg='private_ip only available with on-demand (non-spot) instances')
if boto_supports_param_in_spot_request(ec2, placement_group):
params['placement_group'] = placement_group
elif placement_group :
module.fail_json(
msg="placement_group parameter requires Boto version 2.3.0 or higher.")
params.update(dict(
count = count_remaining,
))
res = ec2.request_spot_instances(spot_price, **params)
# Now we have to do the intermediate waiting
if wait:
spot_req_inst_ids = dict()
spot_wait_timeout = time.time() + spot_wait_timeout
while spot_wait_timeout > time.time():
reqs = ec2.get_all_spot_instance_requests()
for sirb in res:
if sirb.id in spot_req_inst_ids:
continue
for sir in reqs:
if sir.id == sirb.id and sir.instance_id is not None:
spot_req_inst_ids[sirb.id] = sir.instance_id
if len(spot_req_inst_ids) < count:
time.sleep(5)
else:
break
if spot_wait_timeout <= time.time():
module.fail_json(msg = "wait for spot requests timeout on %s" % time.asctime())
instids = spot_req_inst_ids.values()
except boto.exception.BotoServerError, e:
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
if instance_tags:
if instance_tags:
try:
try:
@ -836,15 +911,14 @@ def create_instances(module, ec2, override_count=None):
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message))
# wait here until the instances are up
# wait here until the instances are up
this_res = []
num_running = 0
num_running = 0
wait_timeout = time.time() + wait_timeout
wait_timeout = time.time() + wait_timeout
while wait_timeout > time.time() and num_running < len(instids):
while wait_timeout > time.time() and num_running < len(instids):
res_list = res.connection .get_all_instances(instids)
res_list = ec2 .get_all_instances(instids)
if len(res_list) > 0:
num_running = 0
this_res = res_list[0]
for res in res_list:
num_running = len([ i for i in this_ res.instances if i.state=='running' ])
num_running + = len([ i for i in res.instances if i.state=='running' ])
else :
if len(res_list) <= 0 :
# got a bad response of some sort, possibly due to
# got a bad response of some sort, possibly due to
# stale/cached data. Wait a second and then try again
# stale/cached data. Wait a second and then try again
time.sleep(1)
time.sleep(1)
@ -858,8 +932,9 @@ def create_instances(module, ec2, override_count=None):
# waiting took too long
# waiting took too long
module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
module.fail_json(msg = "wait for instances running timeout on %s" % time.asctime())
for inst in this_res.instances:
#We do this after the loop ends so that we end up with one list
running_instances.append(inst)
for res in res_list:
running_instances.extend(res.instances)
instance_dict_array = []
instance_dict_array = []
created_instance_ids = []
created_instance_ids = []
@ -1019,6 +1094,7 @@ def main():
group_id = dict(type='list'),
group_id = dict(type='list'),
zone = dict(aliases=['aws_zone', 'ec2_zone']),
zone = dict(aliases=['aws_zone', 'ec2_zone']),
instance_type = dict(aliases=['type']),
instance_type = dict(aliases=['type']),
spot_price = dict(),
image = dict(),
image = dict(),
kernel = dict(),
kernel = dict(),
count = dict(default='1'),
count = dict(default='1'),
@ -1026,6 +1102,7 @@ def main():
ramdisk = dict(),
ramdisk = dict(),
wait = dict(type='bool', default=False),
wait = dict(type='bool', default=False),
wait_timeout = dict(default=300),
wait_timeout = dict(default=300),
spot_wait_timeout = dict(default=600),
placement_group = dict(),
placement_group = dict(),
user_data = dict(),
user_data = dict(),
instance_tags = dict(type='dict'),
instance_tags = dict(type='dict'),