Repository: ansible-collections/amazon.aws

Issue:
`ec2_vol` - changing volume type from a type without IOPS (like `standard`) to one with (like `gp3`) raises exception
### Summary

I was trying to modify some existing `standard` volumes (system disks on existing instances) to `gp3`, and `ec2_vol` raised `KeyError` exceptions as it was trying to query the IOPS from the original volumes.

### Issue Type

Bug Report

### Component Name

ec2_vol

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.11.7]
  config file = /home/briantist/code/flan/ansible.cfg
  configured module search path = ['/home/briantist/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/briantist/.a211/lib/python3.6/site-packages/ansible
  ansible collection location = /home/briantist/.ansible/collections
  executable location = /home/briantist/.a211/bin/ansible
  python version = 3.6.9 (default, Jan 26 2021, 15:33:00) [GCC 8.4.0]
  jinja version = 3.0.1
  libyaml = True
```


### Collection Versions

```console (paste below)
$ ansible-galaxy collection list
Collection        Version
----------------- -------
amazon.aws        3.0.0
ansible.posix     1.2.0
community.aws     3.0.1
community.crypto  1.7.1
community.docker  1.7.0
community.general 3.3.2
```


### AWS SDK versions

```console (paste below)
$ pip show boto boto3 botocore
WARNING: Package(s) not found: boto
Name: boto3
Version: 1.18.60
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: /home/briantist/.a211/lib/python3.6/site-packages
Requires: s3transfer, jmespath, botocore
Required-by:
---
Name: botocore
Version: 1.21.60
Summary: Low-level, data-driven core of boto 3.
Home-page: https://github.com/boto/botocore
Author: Amazon Web Services
Author-email:
License: Apache License 2.0
Location: /home/briantist/.a211/lib/python3.6/site-packages
Requires: jmespath, python-dateutil, urllib3
Required-by: s3transfer, boto3
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
DEFAULT_FORKS(env: ANSIBLE_FORKS) = 30
```


### OS / Environment

Ubuntu 18.04 (WSLv2)

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
      amazon.aws.ec2_vol:
        name: "{{ item.tags['Name'] | default(omit) }}"
        instance: "{{ current_instance.instance_id }}"
        device_name: "{{ item.device_mapping.device_name | mandatory }}"
        volume_type: "{{ item.device_mapping.ebs.volume_type | default(omit) }}"
        volume_size: "{{ item.device_mapping.ebs.volume_size | default(omit) }}"
        iops: "{{ item.device_mapping.ebs.iops | default(omit) }}"
        throughput: "{{ item.device_mapping.ebs.throughput | default(omit) }}"
        encrypted: "{{ item.device_mapping.ebs.encrypted | default(omit) }}"
        delete_on_termination: "{{ item.device_mapping.ebs.delete_on_termination | default(omit) }}"
        modify_volume: '{{ aws_modify_existing_ebs_volumes | default(true) }}'
        tags: "{{ item.tags | default(omit) }}"
      loop: "{{
          (
            ec2_launch_volumes
            | default([])
          ) + (
            ec2_provision_volumes
            | default(False)
            | bool
            | ternary(
              ec2_volumes
              | default([]), []
            )
          )
        }}"
```
Some of this relies on retrieving existing instance info,  but the scenario is: existing instance with `standard` type EBS volume on the system disk, and I'm asking `ec2_vol` to make it `gp3` with `iops: 3000` and `throughput: 125`.

### Expected Results

success

### Actual Results

```console (paste below)
The full traceback is:
Traceback (most recent call last):
  File "/home/briantist/.ansible/tmp/ansible-tmp-1642693430.621672-18951-816662666959/AnsiballZ_ec2_vol.py", line 100, in <module>
    _ansiballz_main()
  File "/home/briantist/.ansible/tmp/ansible-tmp-1642693430.621672-18951-816662666959/AnsiballZ_ec2_vol.py", line 92, in _ansiballz_main
    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
  File "/home/briantist/.ansible/tmp/ansible-tmp-1642693430.621672-18951-816662666959/AnsiballZ_ec2_vol.py", line 41, in invoke_module
    run_name='__main__', alter_sys=True)
  File "/usr/lib/python3.6/runpy.py", line 205, in run_module
    return _run_module_code(code, init_globals, run_name, mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
    mod_name, mod_spec, pkg_name, script_name)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/tmp/ansible_amazon.aws.ec2_vol_payload_mms2zmfl/ansible_amazon.aws.ec2_vol_payload.zip/ansible_collections/amazon/aws/plugins/modules/ec2_vol.py", line 878, in <module>
  File "/tmp/ansible_amazon.aws.ec2_vol_payload_mms2zmfl/ansible_amazon.aws.ec2_vol_payload.zip/ansible_collections/amazon/aws/plugins/modules/ec2_vol.py", line 844, in main
  File "/tmp/ansible_amazon.aws.ec2_vol_payload_mms2zmfl/ansible_amazon.aws.ec2_vol_payload.zip/ansible_collections/amazon/aws/plugins/modules/ec2_vol.py", line 385, in update_volume
KeyError: 'iops'
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:

Resolving patch:
diff --git a/plugins/modules/ec2_vol.py b/plugins/modules/ec2_vol.py
--- a/plugins/modules/ec2_vol.py
+++ b/plugins/modules/ec2_vol.py
@@ -381,8 +381,8 @@ def update_volume(module, ec2_conn, volume):
 
         iops_changed = False
         target_iops = module.params.get('iops')
+        original_iops = volume.get('iops')
         if target_iops:
-            original_iops = volume['iops']
             if target_iops != original_iops:
                 iops_changed = True
                 req_obj['Iops'] = target_iops
@@ -394,7 +394,7 @@ def update_volume(module, ec2_conn, volume):
             # otherwise, the default iops value is applied.
             if type_changed and target_type == 'gp3':
                 if (
-                    (volume['iops'] and (int(volume['iops']) < 3000 or int(volume['iops']) > 16000)) or not volume['iops']
+                    (original_iops and (int(original_iops) < 3000 or int(original_iops) > 16000)) or not original_iops
                 ):
                     req_obj['Iops'] = 3000
                     iops_changed = True


Test patch:
diff --git a/tests/integration/targets/ec2_vol/tasks/main.yml b/tests/integration/targets/ec2_vol/tasks/main.yml
--- a/tests/integration/targets/ec2_vol/tasks/main.yml
+++ b/tests/integration/targets/ec2_vol/tasks/main.yml
@@ -286,7 +286,7 @@
         instance: "{{ test_instance.instance_ids[0] }}"
         device_name: /dev/sdh
         volume_size: 1
-        volume_type: gp2
+        volume_type: standard
         name: '{{ resource_prefix }} - sdh'
         tags:
           "lowercase spaced": 'hello cruel world'
@@ -316,7 +316,7 @@
         instance: "{{ test_instance.instance_ids[0] }}"
         device_name: /dev/sdh
         volume_size: 1
-        volume_type: gp2
+        volume_type: standard
         tags:
           ResourcePrefix: "{{ resource_prefix }}"
       check_mode: true
@@ -332,7 +332,7 @@
         instance: "{{ test_instance.instance_ids[0] }}"
         device_name: /dev/sdh
         volume_size: 1
-        volume_type: gp2
+        volume_type: standard
         tags:
           ResourcePrefix: "{{ resource_prefix }}"
       register: new_vol_attach_result_idem
@@ -350,7 +350,7 @@
         id: "{{ new_vol_attach_result.volume.id }}"
         device_name: /dev/sdh
         volume_size: 1
-        volume_type: gp2
+        volume_type: standard
         tags:
           "lowercase spaced": 'hello cruel world ❤️'
           "Title Case": 'Hello Cruel World ❤️'
@@ -370,7 +370,7 @@
           - "'size' in new_vol_attach_result.volume"
           - new_vol_attach_result.volume.size == 1
           - "'volume_type' in new_vol_attach_result"
-          - new_vol_attach_result.volume_type == 'gp2'
+          - new_vol_attach_result.volume_type == 'standard'
           - "'tags' in new_vol_attach_result.volume"
           - (new_vol_attach_result.volume.tags | length) == 6
           - new_vol_attach_result.volume.tags["lowercase spaced"] == 'hello cruel world ❤️'