diff --git a/changelogs/fragments/362-ec2_vol-add-multi-attach-parameter.yml b/changelogs/fragments/362-ec2_vol-add-multi-attach-parameter.yml index 0b79db101c2..fcfb749dbd7 100644 --- a/changelogs/fragments/362-ec2_vol-add-multi-attach-parameter.yml +++ b/changelogs/fragments/362-ec2_vol-add-multi-attach-parameter.yml @@ -1,2 +1,3 @@ minor_changes: - ec2_vol - add parameter ``multi_attach`` to support Multi-Attach on volume creation/update (https://github.com/ansible-collections/amazon.aws/pull/362). +- ec2_vol_info - return ``attachment_set`` is now a list of attachments with Multi-Attach support on disk. (https://github.com/ansible-collections/amazon.aws/pull/362) diff --git a/plugins/modules/ec2_vol.py b/plugins/modules/ec2_vol.py index 805a4db64c5..7a5dd4afd99 100644 --- a/plugins/modules/ec2_vol.py +++ b/plugins/modules/ec2_vol.py @@ -108,6 +108,7 @@ description: - If set to C(yes), Multi-Attach will be enabled when creating the volume. - When you create a new volume, Multi-Attach is disabled by default. + - This parameter is supported with io1 and io2 volumes only. type: bool version_added: 2.0.0 author: "Lester Wade (@lwade)" @@ -197,10 +198,11 @@ # Create new volume with multi-attach enabled - amazon.aws.ec2_vol: - instance: XXXXXX - volume_size: 50 - device_name: sdd + zone: XXXXXX multi_attach: true + volume_size: 4 + volume_type: io1 + iops: 102 # Attach an existing volume to instance. The volume will be deleted upon instance termination. - amazon.aws.ec2_vol: @@ -423,11 +425,14 @@ def update_volume(module, ec2_conn, volume): target_multi_attach = module.params.get('multi_attach') multi_attach_changed = False - if target_multi_attach: - original_multi_attach = volume['multi_attach_enabled'] - if target_multi_attach != original_multi_attach: - multi_attach_changed = True - req_obj['MultiAttachEnabled'] = target_multi_attach + if target_multi_attach is not None: + if "io1" in [target_type, original_type] or "io2" in [target_type, original_type]: + original_multi_attach = volume['multi_attach_enabled'] + if target_multi_attach != original_multi_attach: + multi_attach_changed = True + req_obj['MultiAttachEnabled'] = target_multi_attach + else: + module.warn("multi_attach is supported with io1 and io2 volumes only.") changed = iops_changed or size_changed or type_changed or throughput_changed or multi_attach_changed @@ -484,8 +489,12 @@ def create_volume(module, ec2_conn, zone): if throughput: additional_params['Throughput'] = int(throughput) - if multi_attach: - additional_params['MultiAttachEnabled'] = multi_attach + + if multi_attach is True: + if volume_type not in ("io1", "io2"): + module.warn("multi_attach is supported with io1 and io2 volumes only.") + else: + additional_params['MultiAttachEnabled'] = multi_attach create_vol_response = ec2_conn.create_volume( aws_retry=True, @@ -517,7 +526,7 @@ def attach_volume(module, ec2_conn, volume_dict, instance_dict, device_name): attachment_data = get_attachment_data(volume_dict, wanted_state='attached') if attachment_data: - if not volume_dict['multi_attach']: + if not volume_dict['multi_attach_enabled']: # volumes without MultiAttach Enabled can be attached to 1 instance only if attachment_data[0].get('instance_id', None) != instance_dict['instance_id']: module.fail_json(msg="Volume {0} is already attached to another instance: {1}".format(volume_dict['volume_id'], @@ -601,7 +610,7 @@ def get_attachment_data(volume_dict, wanted_state=None): 'device': data.get('device', None), 'instance_id': data.get('instance_id', None), 'status': data.get('state', None), - 'deleteOnTermination': data.get('delete_on_termination', None) + 'delete_on_termination': data.get('delete_on_termination', None) }) return attachment_data @@ -639,6 +648,7 @@ def get_volume_info(module, volume, tags=None): 'type': volume.get('volume_type'), 'zone': volume.get('availability_zone'), 'attachment_set': attachment_data, + 'multi_attach_enabled': volume.get('multi_attach_enabled'), 'tags': tags } diff --git a/plugins/modules/ec2_vol_info.py b/plugins/modules/ec2_vol_info.py index ba20d45ee4f..45238ff9c70 100644 --- a/plugins/modules/ec2_vol_info.py +++ b/plugins/modules/ec2_vol_info.py @@ -109,6 +109,10 @@ description: The Availability Zone of the volume. type: str sample: "us-east-1b" + throughput: + description: The throughput that the volume supports, in MiB/s. + type: int + sample: 131 ''' try: @@ -128,6 +132,16 @@ def get_volume_info(volume, region): attachment = volume["attachments"] + attachment_data = [] + for data in volume["attachments"]: + attachment_data.append({ + 'attach_time': data.get('attach_time', None), + 'device': data.get('device', None), + 'instance_id': data.get('instance_id', None), + 'status': data.get('state', None), + 'delete_on_termination': data.get('delete_on_termination', None) + }) + volume_info = { 'create_time': volume["create_time"], 'id': volume["volume_id"], @@ -139,16 +153,13 @@ def get_volume_info(volume, region): 'type': volume["volume_type"], 'zone': volume["availability_zone"], 'region': region, - 'attachment_set': { - 'attach_time': attachment[0]["attach_time"] if len(attachment) > 0 else None, - 'device': attachment[0]["device"] if len(attachment) > 0 else None, - 'instance_id': attachment[0]["instance_id"] if len(attachment) > 0 else None, - 'status': attachment[0]["state"] if len(attachment) > 0 else None, - 'delete_on_termination': attachment[0]["delete_on_termination"] if len(attachment) > 0 else None - }, + 'attachment_set': attachment_data, 'tags': boto3_tag_list_to_ansible_dict(volume['tags']) if "tags" in volume else None } + if 'throughput' in volume: + volume_info['throughput'] = volume["throughput"] + return volume_info diff --git a/tests/integration/targets/ec2_vol/tasks/tests.yml b/tests/integration/targets/ec2_vol/tasks/tests.yml index 6dfe1ec77ad..938152e9ce0 100644 --- a/tests/integration/targets/ec2_vol/tasks/tests.yml +++ b/tests/integration/targets/ec2_vol/tasks/tests.yml @@ -52,7 +52,7 @@ set_fact: ec2_ami_image: '{{ latest_ami.image_id }}' - # ==== ec2_vol tests =============================================== + # # ==== ec2_vol tests =============================================== - name: create a volume (validate module defaults) ec2_vol: @@ -187,9 +187,7 @@ - vol_attach_result.volume.attachment_set[0].status in ['attached', 'attaching'] - vol_attach_result.volume.attachment_set[0].instance_id == test_instance.instance_ids[0] - vol_attach_result.volume.attachment_set[0].device == '/dev/sdg' - -# Failing -# - "vol_attach_result.volume.attachment_set.deleteOnTermination" + - not vol_attach_result.volume.attachment_set[0].delete_on_termination - name: attach existing volume to an instance (idempotent) ec2_vol: @@ -203,7 +201,7 @@ assert: that: - "not vol_attach_result.changed" - - vol_attach_result.volume.attachment_set[0].status in ['attached', 'attaching'] + - "vol_attach_result.volume.attachment_set[0].status == 'attached'" - name: attach a new volume to an instance ec2_vol: @@ -453,7 +451,7 @@ - name: volume type must be gp3 assert: that: - - v.type == 'gp3' + - v.type == 'gp3' vars: v: "{{ verify_gp3_change.volumes[0] }}" @@ -500,8 +498,7 @@ that: - dot_volume.changed - "'attachment_set' in dot_volume.volume" - - "'deleteOnTermination' in dot_volume.volume.attachment_set" - - "dot_volume.volume.attachment_set.deleteOnTermination is defined" + - "'delete_on_termination' in dot_volume.volume.attachment_set[0]" - "'create_time' in dot_volume.volume" - "'id' in dot_volume.volume" - "'size' in dot_volume.volume" @@ -569,7 +566,7 @@ zone: "{{ availability_zone }}" volume_type: gp3 throughput: 130 - iops: 3001 + iops: 300 name: "GP3-TEST-{{ resource_prefix }}" tags: ResourcePrefix: "{{ resource_prefix }}" @@ -580,8 +577,7 @@ that: - gp3_volume.changed - "'attachment_set' in gp3_volume.volume" - - "'deleteOnTermination' in gp3_volume.volume.attachment_set" - - gp3_volume.volume.attachment_set.deleteOnTermination == none + - "'delete_on_termination' in gp3_volume.volume.attachment_set[0]" - "'create_time' in gp3_volume.volume" - "'id' in gp3_volume.volume" - "'size' in gp3_volume.volume" @@ -589,13 +585,30 @@ - "'volume_type' in gp3_volume" - gp3_volume.volume_type == 'gp3' - "'iops' in gp3_volume.volume" - - gp3_volume.volume.iops == 3001 + - gp3_volume.volume.iops == 300 - "'throughput' in gp3_volume.volume" - gp3_volume.volume.throughput == 130 - "'tags' in gp3_volume.volume" - (gp3_volume.volume.tags | length ) == 2 - gp3_volume.volume.tags["ResourcePrefix"] == "{{ resource_prefix }}" + - name: Read volume information to validate throughput + ec2_vol_info: + filters: + volume-id: "{{ gp3_volume.volume_id }}" + register: verify_throughput + + - name: throughput must be equal to 130 + assert: + that: + - v.throughput == 130 + vars: + v: "{{ verify_throughput.volumes[0] }}" + + - name: print out facts + debug: + var: vol_facts + - name: increase throughput ec2_vol: volume_size: 7 @@ -613,8 +626,7 @@ that: - gp3_volume.changed - "'attachment_set' in gp3_volume.volume" - - "'deleteOnTermination' in gp3_volume.volume.attachment_set" - - gp3_volume.volume.attachment_set.deleteOnTermination == none + - "'delete_on_termination' in gp3_volume.volume.attachment_set[0]" - "'create_time' in gp3_volume.volume" - "'id' in gp3_volume.volume" - "'size' in gp3_volume.volume" @@ -622,13 +634,68 @@ - "'volume_type' in gp3_volume" - gp3_volume.volume_type == 'gp3' - "'iops' in gp3_volume.volume" - - gp3_volume.volume.iops == 3001 + - gp3_volume.volume.iops == 300 - "'throughput' in gp3_volume.volume" - gp3_volume.volume.throughput == 131 - - "'tags' in gp3_volume.volume" - - (gp3_volume.volume.tags | length ) == 2 - - gp3_volume.volume.tags["ResourcePrefix"] == "{{ resource_prefix }}" + + # Multi-Attach disk + - name: create disk with multi-attach enabled + ec2_vol: + volume_size: 4 + volume_type: io1 + iops: 102 + zone: "{{ availability_zone }}" + multi_attach: yes + tags: + ResourcePrefix: "{{ resource_prefix }}" + register: multi_attach_disk + + - name: check volume creation + assert: + that: + - multi_attach_disk.changed + - "'volume' in multi_attach_disk" + - multi_attach_disk.volume.multi_attach_enabled + + - name: attach existing volume to an instance + ec2_vol: + id: "{{ multi_attach_disk.volume_id }}" + instance: "{{ test_instance.instance_ids[0] }}" + device_name: /dev/sdk + delete_on_termination: no + register: vol_attach_result + + - name: create another ec2 instance + ec2_instance: + name: "{{ resource_prefix }}-2" + vpc_subnet_id: "{{ testing_subnet.subnet.id }}" + instance_type: t3.nano + image_id: "{{ ec2_ami_image }}" + tags: + ResourcePrefix: "{{ resource_prefix }}" + register: test_instance_2 + + - name: check task return attributes + assert: + that: + - test_instance_2.changed + + - name: attach existing volume to second instance + ec2_vol: + id: "{{ multi_attach_disk.volume_id }}" + instance: "{{ test_instance_2.instance_ids[0] }}" + device_name: /dev/sdg + delete_on_termination: no + register: vol_attach_result + - name: check task return attributes + assert: + that: + - vol_attach_result.changed + - "'volume' in vol_attach_result" + - vol_attach_result.volume.attachment_set | length == 2 + - 'test_instance.instance_ids[0] in vol_attach_result.volume.attachment_set | map(attribute="instance_id") | list' + - 'test_instance_2.instance_ids[0] in vol_attach_result.volume.attachment_set | map(attribute="instance_id") | list' # ==== Cleanup ============================================================ @@ -636,8 +703,11 @@ - name: Describe the instance before we delete it ec2_instance_info: instance_ids: - - "{{ test_instance.instance_ids[0] }}" + - "{{ item }}" ignore_errors: yes + with_items: + - "{{ test_instance.instance_ids[0] }}" + - "{{ test_instance_2.instance_ids[0] }}" register: pre_delete - debug: @@ -646,8 +716,11 @@ - name: delete test instance ec2_instance: instance_ids: - - "{{ test_instance.instance_ids[0] }}" + - "{{ item }}" state: terminated + with_items: + - "{{ test_instance.instance_ids[0] }}" + - "{{ test_instance_2.instance_ids[0] }}" ignore_errors: yes - name: delete volumes @@ -663,6 +736,7 @@ - "{{ attach_new_vol_from_snapshot_result }}" - "{{ dot_volume }}" - "{{ gp3_volume }}" + - "{{ multi_attach_disk }}" - name: delete snapshot ec2_snapshot: