From 6ef3fab1fdb47b5fd7b1b1dfb91008d1ffc2ec63 Mon Sep 17 00:00:00 2001 From: Eugene Brevdo Date: Fri, 13 Dec 2013 13:45:42 -0800 Subject: [PATCH] ec2_vol supports name / id to mount volume on instance * volume_size no longer required if name/id are provided * id is volume-id * name is volume Name tag * special checking is provided --- cloud/ec2_vol | 105 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 14 deletions(-) diff --git a/cloud/ec2_vol b/cloud/ec2_vol index 71a60ac603c..511bdd0cea5 100644 --- a/cloud/ec2_vol +++ b/cloud/ec2_vol @@ -46,6 +46,18 @@ options: required: false default: null aliases: [] + name: + description: + - volume Name tag if you wish to attach an existing volume (requires instance) + required: false + default: null + aliases: [] + name: + description: + - volume id if you wish to attach an existing volume (requires instance) + required: false + default: null + aliases: [] volume_size: description: - size of volume (in GB) to create. @@ -122,6 +134,26 @@ EXAMPLES = ''' volume_size: 5 with_items: ec2.instances register: ec2_vol + +# Idempotent playbook example combined with single instance launch +# Volume must exist in the same zone; will not do anything if it is +# already attached. +- local_action: + module: ec2 + keypair: "{{ keypair }}" + image: "{{ image }}" + zone: YYYYYY + id: my_instance + wait: yes + count: 1 + register: ec2 +- local_action: + module: ec2_vol + instance: "{{ item.id }}" + name: my_existing_volume_Name_tag + device_name: /dev/xvdf + with_items: ec2.instances + register: ec2_vol ''' # Note: this module needs to be made idempotent. Possible solution is to use resource tags with the volumes. @@ -142,7 +174,9 @@ def main(): module = AnsibleModule( argument_spec = dict( instance = dict(), - volume_size = dict(required=True), + id = dict(), + name = dict(), + volume_size = dict(), iops = dict(), device_name = dict(), region = dict(aliases=['aws_region', 'ec2_region'], choices=AWS_REGIONS), @@ -154,20 +188,27 @@ def main(): ) ) + id = module.params.get('id') + name = module.params.get('name') instance = module.params.get('instance') volume_size = module.params.get('volume_size') iops = module.params.get('iops') device_name = module.params.get('device_name') zone = module.params.get('zone') snapshot = module.params.get('snapshot') - + ec2 = ec2_connect(module) + if id and name: + module.fail_json(msg="Both id and name cannot be specified") + + if not (id or name or volume_size): + module.fail_json(msg="Cannot specify volume_size and either one of name or id") + # Here we need to get the zone info for the instance. This covers situation where # instance is specified but zone isn't. # Useful for playbooks chaining instance launch with volume create + attach and where the # zone doesn't matter to the user. - if instance: reservation = ec2.get_all_instances(instance_ids=instance) inst = reservation[0].instances[0] @@ -189,14 +230,51 @@ def main(): volume_type = 'standard' # If no instance supplied, try volume creation based on module parameters. + if name or id: + if not instance: + module.fail_json(msg = "If name or id is specified, instance must also be specified") + if iops or volume_size: + module.fail_json(msg = "Parameters are not compatible: [id or name] and [iops or volume_size]") - try: - volume = ec2.create_volume(volume_size, zone, snapshot, volume_type, iops) - while volume.status != 'available': - time.sleep(3) - volume.update() - except boto.exception.BotoServerError, e: - module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + filters = {} + volume_ids = None + if zone: + filters['availability_zone'] = zone + if name: + filters = {'tag:Name': name} + if id: + volume_ids = [id] + try: + vols = ec2.get_all_volumes(volume_ids=volume_ids, filters=filters) + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + + if not vols: + module.fail_json(msg = "Could not find volume in zone (if specified): %s" % name or id) + if len(vols) > 1: + module.fail_json(msg = + "Found more than one volume in zone (if specified) with name: %s" % name) + + volume = vols.pop() + if volume.attachment_state() is not None: + adata = volume.attach_data + if adata.instance_id != instance: + module.fail_json(msg = "Volume %s is already attached to another instance: %s" + % (name or id, adata.instance_id)) + else: + module.exit_json(msg="Volume %s is already mapped on instance %s: %s" % + (name or id, adata.instance_id, adata.device), + volume_id=id, + device=adata.device, + changed=False) + else: + try: + volume = ec2.create_volume(volume_size, zone, snapshot, volume_type, iops) + while volume.status != 'available': + time.sleep(3) + volume.update() + except boto.exception.BotoServerError, e: + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) # Attach the created volume. @@ -207,16 +285,15 @@ def main(): time.sleep(3) volume.update() except boto.exception.BotoServerError, e: - module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) - + module.fail_json(msg = "%s: %s" % (e.error_code, e.error_message)) + # If device_name isn't set, make a choice based on best practices here: # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html - + # In future this needs to be more dynamic but combining block device mapping best practices # (bounds for devices, as above) with instance.block_device_mapping data would be tricky. For me ;) # Use password data attribute to tell whether the instance is Windows or Linux - if device_name is None and instance: try: if not ec2.get_password_data(inst.id):