From de659af82b2fa39a88e8e7e7624e9a62a909180d Mon Sep 17 00:00:00 2001 From: willthames Date: Wed, 16 Oct 2013 15:37:24 +1000 Subject: [PATCH] Added the ability to add volumes to instances at creation time This allows a volume to be cloned from a snapshot, a brand new volume to be created, or an ephemeral volumes to be associated at time of instance creation. This avoids any race conditions associated with creating extra volumes after instance creation (e.g. writes happening before the volume is mounted). In addition, this allows the root volume to be edited --- library/cloud/ec2 | 79 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/library/cloud/ec2 b/library/cloud/ec2 index 7ec2c0c6472..c8b8b503f9d 100644 --- a/library/cloud/ec2 +++ b/library/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'), ) )