diff --git a/cloud/ec2 b/cloud/ec2 index 7ec2c0c6472..c8b8b503f9d 100644 --- a/cloud/ec2 +++ b/cloud/ec2 @@ -191,6 +191,13 @@ options: required: false default: 'present' aliases: [] + volumes: + version_added: "1.5" + description: + - a list of volume dicts, each containing device name and optionally ephemeral id or snapshot id. Size and type (and number of iops for io device type) must be specified for a new volume or a root volume, and may be passed for a snapshot volume. For any volume, a volume size less than 1 will be interpreted as a request not to create the volume. + required: false + default: null + aliases: [] requirements: [ "boto" ] author: Seth Vidal, Tim Gerla, Lester Wade @@ -223,6 +230,23 @@ EXAMPLES = ''' instance_tags: '{"db":"postgres"}' monitoring=yes +# Single instance with additional IOPS volume from snapshot +local_action: + module: ec2 + keypair: mykey + group: webserver + instance_type: m1.large + image: ami-6e649707 + wait: yes + wait_timeout: 500 + volumes: + - device_name: /dev/sdb + snapshot: snap-abcdef12 + device_type: io1 + iops: 1000 + volume_size: 100 + monitoring=yes + # Multiple groups example local_action: module: ec2 @@ -236,6 +260,22 @@ local_action: instance_tags: '{"db":"postgres"}' monitoring=yes +# Multiple instances with additional volume from snapshot +local_action: + module: ec2 + keypair: mykey + group: webserver + instance_type: m1.large + image: ami-6e649707 + wait: yes + wait_timeout: 500 + count: 5 + volumes: + - device_name: /dev/sdb + snapshot: snap-abcdef12 + volume_size: 10 + monitoring=yes + # VPC example - local_action: module: ec2 @@ -296,6 +336,7 @@ import time try: import boto.ec2 + from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping from boto.exception import EC2ResponseError except ImportError: print "failed=True msg='boto required for this module'" @@ -364,6 +405,30 @@ def boto_supports_profile_name_arg(ec2): run_instances_method = getattr(ec2, 'run_instances') return 'instance_profile_name' in run_instances_method.func_code.co_varnames +def create_block_device(module, ec2, volume): + # Not aware of a way to determine this programatically + # http://aws.amazon.com/about-aws/whats-new/2013/10/09/ebs-provisioned-iops-maximum-iops-gb-ratio-increased-to-30-1/ + MAX_IOPS_TO_SIZE_RATIO = 30 + if 'snapshot' not in volume and 'ephemeral' not in volume: + if 'volume_size' not in volume: + module.fail_json(msg = 'Size must be specified when creating a new volume or modifying the root volume') + if 'snapshot' in volume: + if 'device_type' in volume and volume.get('device_type') == 'io1' and 'iops' not in volume: + module.fail_json(msg = 'io1 volumes must have an iops value set') + if 'iops' in volume: + snapshot = ec2.get_all_snapshots(snapshot_ids=[volume['snapshot']])[0] + size = volume.get('volume_size', snapshot.volume_size) + if int(volume['iops']) > MAX_IOPS_TO_SIZE_RATIO * size: + module.fail_json(msg = 'IOPS must be at most %d times greater than size' % MAX_IOPS_TO_SIZE_RATIO) + if 'ephemeral' in volume: + if 'snapshot' in volume: + module.fail_json(msg = 'Cannot set both ephemeral and snapshot') + return BlockDeviceType(snapshot_id=volume.get('snapshot'), + ephemeral_name=volume.get('ephemeral'), + size=volume.get('volume_size'), + volume_type=volume.get('device_type'), + delete_on_termination=volume.get('delete_on_termination', False), + iops=volume.get('iops')) def create_instances(module, ec2): """ @@ -397,6 +462,7 @@ def create_instances(module, ec2): assign_public_ip = module.boolean(module.params.get('assign_public_ip')) private_ip = module.params.get('private_ip') instance_profile_name = module.params.get('instance_profile_name') + volumes = module.params.get('volumes') # group_id and group_name are exclusive of each other if group_id and group_name: @@ -489,6 +555,18 @@ def create_instances(module, ec2): else: params['security_groups'] = group_name + if volumes: + bdm = BlockDeviceMapping() + for volume in volumes: + if 'device_name' not in volume: + module.fail_json(msg = 'Device name must be set for volume') + # Minimum volume size is 1GB. We'll use volume size explicitly set to 0 + # to be a signal not to create this volume + if 'volume_size' not in volume or int(volume['volume_size']) > 0: + bdm[volume['device_name']] = create_block_device(module, ec2, volume) + + params['block_device_map'] = bdm + res = ec2.run_instances(**params) except boto.exception.BotoServerError, e: module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) @@ -641,6 +719,7 @@ def main(): instance_profile_name = dict(), instance_ids = dict(type='list'), state = dict(default='present'), + volumes = dict(type='list'), ) )