Repository: ansible/ansible

Issue:
Unstable integration test ec2_vpc_subnet
##### ISSUE TYPE

Bug Report

##### COMPONENT NAME

ec2_vpc_subnet integration test

##### ANSIBLE VERSION

2.5

##### CONFIGURATION

N/A

##### OS / ENVIRONMENT

Shippable

##### SUMMARY

Unstable integration test ec2_vpc_subnet:

https://app.shippable.com/github/ansible/ansible/runs/54187/55/tests

##### STEPS TO REPRODUCE

Run tests on Shippable.

##### EXPECTED RESULTS

Tests pass.

##### ACTUAL RESULTS

Tests are unstable or fail.

> test/integration/targets/ec2_vpc_subnet/tasks/main.yml:350 / [localhost] amazon: ec2_vpc_subnet : tidy up subnet aws_secret_key={{ aws_secret_key }}, aws_access_key={{ aws_access_key }}, security_token={{ security_token }}, state=absent, vpc_id={{ vpc_result.vpc.id }}, region={{ ec2_region }}, cidr=10.232.232.128/28

> failure: The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'vpc' The error appears to have been in '/root/ansible/test/integration/targets/ec2_vpc_subnet/tasks/main.yml': line 350, column 7, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: - name: tidy up subnet ^ here

```
{
"failed": true, 
"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'vpc'\n\nThe error appears to have been in '/root/ansible/test/integration/targets/ec2_vpc_subnet/tasks/main.yml': line 350, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n    - name: tidy up subnet\n      ^ here\n"
}
```

> test/integration/targets/ec2_vpc_subnet/tasks/main.yml:14 / [localhost] amazon: ec2_vpc_subnet : create a VPC aws_secret_key={{ aws_secret_key }}, aws_access_key={{ aws_access_key }}, name={{ resource_prefix }}-vpc, security_token={{ security_token }}, tags={u'Name': u'{{ resource_prefix }}-vpc', u'Description': u'Created by ansible-test'}, region={{ ec2_region }}, state=present, cidr_block=10.232.232.128/26

> error: ClientError: An error occurred (InvalidVpcID.NotFound) when calling the DescribeVpcClassicLink operation: The vpc ID 'vpc-1b2bda60' does not exist

```
Traceback (most recent call last):
  File "/tmp/ansible_dPE6PL/ansible_module_ec2_vpc_net.py", line 203, in get_vpc
    classic_link = connection.describe_vpc_classic_link(VpcIds=[vpc_id])['Vpcs'][0].get('ClassicLinkEnabled')
  File "/usr/local/lib/python2.7/dist-packages/botocore/client.py", line 317, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/usr/local/lib/python2.7/dist-packages/botocore/client.py", line 615, in _make_api_call
    raise error_class(parsed_response, operation_name)
ClientError: An error occurred (InvalidVpcID.NotFound) when calling the DescribeVpcClassicLink operation: The vpc ID 'vpc-1b2bda60' does not exist
```


Discussion:

Resolving patch:
diff --git a/lib/ansible/module_utils/aws/waiters.py b/lib/ansible/module_utils/aws/waiters.py
new file mode 100644
--- /dev/null
+++ b/lib/ansible/module_utils/aws/waiters.py
@@ -0,0 +1,178 @@
+# Copyright: (c) 2018, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+try:
+    import botocore.waiter as core_waiter
+except ImportError:
+    pass  # caught by HAS_BOTO3
+
+
+ec2_data = {
+    "version": 2,
+    "waiters": {
+        "RouteTableExists": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeRouteTables",
+            "acceptors": [
+                {
+                    "matcher": "path",
+                    "expected": True,
+                    "argument": "length(RouteTables[]) > `0`",
+                    "state": "success"
+                },
+                {
+                    "matcher": "error",
+                    "expected": "InvalidRouteTableID.NotFound",
+                    "state": "retry"
+                },
+            ]
+        },
+        "SubnetExists": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "path",
+                    "expected": True,
+                    "argument": "length(Subnets[]) > `0`",
+                    "state": "success"
+                },
+                {
+                    "matcher": "error",
+                    "expected": "InvalidSubnetID.NotFound",
+                    "state": "retry"
+                },
+            ]
+        },
+        "SubnetHasMapPublic": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "pathAll",
+                    "expected": True,
+                    "argument": "Subnets[].MapPublicIpOnLaunch",
+                    "state": "success"
+                },
+            ]
+        },
+        "SubnetNoMapPublic": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "pathAll",
+                    "expected": False,
+                    "argument": "Subnets[].MapPublicIpOnLaunch",
+                    "state": "success"
+                },
+            ]
+        },
+        "SubnetHasAssignIpv6": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "pathAll",
+                    "expected": True,
+                    "argument": "Subnets[].AssignIpv6AddressOnCreation",
+                    "state": "success"
+                },
+            ]
+        },
+        "SubnetNoAssignIpv6": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "pathAll",
+                    "expected": False,
+                    "argument": "Subnets[].AssignIpv6AddressOnCreation",
+                    "state": "success"
+                },
+            ]
+        },
+        "SubnetDeleted": {
+            "delay": 5,
+            "maxAttempts": 40,
+            "operation": "DescribeSubnets",
+            "acceptors": [
+                {
+                    "matcher": "path",
+                    "expected": True,
+                    "argument": "length(Subnets[]) > `0`",
+                    "state": "retry"
+                },
+                {
+                    "matcher": "error",
+                    "expected": "InvalidSubnetID.NotFound",
+                    "state": "success"
+                },
+            ]
+        },
+    }
+}
+
+
+def model_for(name):
+    ec2_models = core_waiter.WaiterModel(waiter_config=ec2_data)
+    return ec2_models.get_waiter(name)
+
+
+waiters_by_name = {
+    ('EC2', 'route_table_exists'): lambda ec2: core_waiter.Waiter(
+        'route_table_exists',
+        model_for('RouteTableExists'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_route_tables
+        )),
+    ('EC2', 'subnet_exists'): lambda ec2: core_waiter.Waiter(
+        'subnet_exists',
+        model_for('SubnetExists'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+    ('EC2', 'subnet_has_map_public'): lambda ec2: core_waiter.Waiter(
+        'subnet_has_map_public',
+        model_for('SubnetHasMapPublic'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+    ('EC2', 'subnet_no_map_public'): lambda ec2: core_waiter.Waiter(
+        'subnet_no_map_public',
+        model_for('SubnetNoMapPublic'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+    ('EC2', 'subnet_has_assign_ipv6'): lambda ec2: core_waiter.Waiter(
+        'subnet_has_assign_ipv6',
+        model_for('SubnetHasAssignIpv6'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+    ('EC2', 'subnet_no_assign_ipv6'): lambda ec2: core_waiter.Waiter(
+        'subnet_no_assign_ipv6',
+        model_for('SubnetNoAssignIpv6'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+    ('EC2', 'subnet_deleted'): lambda ec2: core_waiter.Waiter(
+        'subnet_deleted',
+        model_for('SubnetDeleted'),
+        core_waiter.NormalizedOperationMethod(
+            ec2.describe_subnets
+        )),
+}
+
+
+def get_waiter(client, waiter_name):
+    try:
+        return waiters_by_name[(client.__class__.__name__, waiter_name)](client)
+    except KeyError:
+        raise NotImplementedError("Waiter {0} could not be found for client {1}. Available waiters: {2}".format(
+            waiter_name, type(client), ', '.join(repr(k) for k in waiters_by_name.keys())))
diff --git a/lib/ansible/modules/cloud/amazon/ec2_vpc_route_table.py b/lib/ansible/modules/cloud/amazon/ec2_vpc_route_table.py
--- a/lib/ansible/modules/cloud/amazon/ec2_vpc_route_table.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_vpc_route_table.py
@@ -224,7 +224,9 @@
 '''
 
 import re
+from time import sleep
 from ansible.module_utils.aws.core import AnsibleAWSModule
+from ansible.module_utils.aws.waiters import get_waiter
 from ansible.module_utils.ec2 import ec2_argument_spec, boto3_conn, get_aws_connection_info
 from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list
 from ansible.module_utils.ec2 import camel_dict_to_snake_dict, snake_dict_to_camel_dict
@@ -647,6 +649,12 @@ def ensure_route_table_present(connection, module):
         if not module.check_mode:
             try:
                 route_table = connection.create_route_table(VpcId=vpc_id)['RouteTable']
+                # try to wait for route table to be present before moving on
+                get_waiter(
+                    connection, 'route_table_exists'
+                ).wait(
+                    RouteTableIds=[route_table['RouteTableId']],
+                )
             except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                 module.fail_json_aws(e, msg="Error creating route table")
         else:
@@ -678,6 +686,9 @@ def ensure_route_table_present(connection, module):
                                             purge_subnets=purge_subnets)
         changed = changed or result['changed']
 
+    if changed:
+        # pause to allow route table routes/subnets/associations to be updated before exiting with final state
+        sleep(5)
     module.exit_json(changed=changed, route_table=get_route_table_info(connection, module, route_table))
 
 
diff --git a/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py b/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py
--- a/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_vpc_subnet.py
@@ -216,13 +216,16 @@
 
 import time
 import traceback
+from distutils.version import LooseVersion
 
 try:
     import botocore
 except ImportError:
-    pass  # caught by imported boto3
+    pass  # caught by AnsibleAWSModule
 
+from ansible.module_utils._text import to_text
 from ansible.module_utils.aws.core import AnsibleAWSModule
+from ansible.module_utils.aws.waiters import get_waiter
 from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list, ansible_dict_to_boto3_tag_list,
                                       ec2_argument_spec, camel_dict_to_snake_dict, get_aws_connection_info,
                                       boto3_conn, boto3_tag_list_to_ansible_dict, compare_aws_tags, AWSRetry)
@@ -262,7 +265,25 @@ def describe_subnets_with_backoff(client, **params):
     return client.describe_subnets(**params)
 
 
-def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None):
+def waiter_params(module, params, start_time):
+    if LooseVersion(botocore.__version__) >= "1.7.0":
+        remaining_wait_timeout = int(module.params['wait_timeout'] + start_time - time.time())
+        params['WaiterConfig'] = {'Delay': 5, 'MaxAttempts': remaining_wait_timeout // 5}
+    return params
+
+
+def handle_waiter(conn, module, waiter_name, params, start_time):
+    try:
+        get_waiter(conn, waiter_name).wait(
+            **waiter_params(module, params, start_time)
+        )
+    except botocore.exceptions.WaiterError as e:
+        module.fail_json_aws(e, "Failed to wait for updates to complete")
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, "An exception happened while trying to wait for updates")
+
+
+def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None, start_time=None):
     wait = module.params['wait']
     wait_timeout = module.params['wait_timeout']
 
@@ -284,20 +305,19 @@ def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None):
     # new subnets's id to do things like create tags results in
     # exception.
     if wait and subnet.get('state') != 'available':
-        delay = 5
-        max_attempts = wait_timeout / delay
-        waiter_config = dict(Delay=delay, MaxAttempts=max_attempts)
-        waiter = conn.get_waiter('subnet_available')
+        handle_waiter(conn, module, 'subnet_exists', {'SubnetIds': [subnet['id']]}, start_time)
         try:
-            waiter.wait(SubnetIds=[subnet['id']], WaiterConfig=waiter_config)
+            conn.get_waiter('subnet_available').wait(
+                **waiter_params(module, {'SubnetIds': [subnet['id']]}, start_time)
+            )
             subnet['state'] = 'available'
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
-            module.fail_json(msg="Create subnet action timed out waiting for Subnet to become available.")
+            module.fail_json_aws(e, "Create subnet action timed out waiting for subnet to become available")
 
     return subnet
 
 
-def ensure_tags(conn, module, subnet, tags, purge_tags):
+def ensure_tags(conn, module, subnet, tags, purge_tags, start_time):
     changed = False
 
     filters = ansible_dict_to_boto3_filter_list({'resource-id': subnet['id'], 'resource-type': 'subnet'})
@@ -311,7 +331,12 @@ def ensure_tags(conn, module, subnet, tags, purge_tags):
     if to_update:
         try:
             if not module.check_mode:
-                conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_update))
+                AWSRetry.exponential_backoff(
+                    catch_extra_error_codes=['InvalidSubnetID.NotFound']
+                )(conn.create_tags)(
+                    Resources=[subnet['id']],
+                    Tags=ansible_dict_to_boto3_tag_list(to_update)
+                )
 
             changed = True
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
@@ -324,16 +349,24 @@ def ensure_tags(conn, module, subnet, tags, purge_tags):
                 for key in to_delete:
                     tags_list.append({'Key': key})
 
-                conn.delete_tags(Resources=[subnet['id']], Tags=tags_list)
+                AWSRetry.exponential_backoff(
+                    catch_extra_error_codes=['InvalidSubnetID.NotFound']
+                )(conn.delete_tags)(Resources=[subnet['id']], Tags=tags_list)
 
             changed = True
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
             module.fail_json_aws(e, msg="Couldn't delete tags")
 
+    if module.params['wait'] and not module.check_mode:
+        # Wait for tags to be updated
+        filters = [{'Name': 'tag:{0}'.format(k), 'Values': [v]} for k, v in tags.items()]
+        handle_waiter(conn, module, 'subnet_exists',
+                      {'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
+
     return changed
 
 
-def ensure_map_public(conn, module, subnet, map_public, check_mode):
+def ensure_map_public(conn, module, subnet, map_public, check_mode, start_time):
     if check_mode:
         return
     try:
@@ -342,19 +375,18 @@ def ensure_map_public(conn, module, subnet, map_public, check_mode):
         module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
 
 
-def ensure_assign_ipv6_on_create(conn, module, subnet, assign_instances_ipv6, check_mode):
+def ensure_assign_ipv6_on_create(conn, module, subnet, assign_instances_ipv6, check_mode, start_time):
     if check_mode:
         return
-
     try:
         conn.modify_subnet_attribute(SubnetId=subnet['id'], AssignIpv6AddressOnCreation={'Value': assign_instances_ipv6})
     except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
         module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
 
 
-def disassociate_ipv6_cidr(conn, module, subnet):
+def disassociate_ipv6_cidr(conn, module, subnet, start_time):
     if subnet.get('assign_ipv6_address_on_creation'):
-        ensure_assign_ipv6_on_create(conn, module, subnet, False, False)
+        ensure_assign_ipv6_on_create(conn, module, subnet, False, False, start_time)
 
     try:
         conn.disassociate_subnet_cidr_block(AssociationId=subnet['ipv6_association_id'])
@@ -362,13 +394,23 @@ def disassociate_ipv6_cidr(conn, module, subnet):
         module.fail_json_aws(e, msg="Couldn't disassociate ipv6 cidr block id {0} from subnet {1}"
                              .format(subnet['ipv6_association_id'], subnet['id']))
 
+    # Wait for cidr block to be disassociated
+    if module.params['wait']:
+        filters = ansible_dict_to_boto3_filter_list(
+            {'ipv6-cidr-block-association.state': ['disassociated'],
+             'vpc-id': subnet['vpc_id']}
+        )
+        handle_waiter(conn, module, 'subnet_exists',
+                      {'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
+
 
-def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
+def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode, start_time):
+    wait = module.params['wait']
     changed = False
 
     if subnet['ipv6_association_id'] and not ipv6_cidr:
         if not check_mode:
-            disassociate_ipv6_cidr(conn, module, subnet)
+            disassociate_ipv6_cidr(conn, module, subnet, start_time)
         changed = True
 
     if ipv6_cidr:
@@ -385,7 +427,7 @@ def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
 
         if subnet['ipv6_association_id']:
             if not check_mode:
-                disassociate_ipv6_cidr(conn, module, subnet)
+                disassociate_ipv6_cidr(conn, module, subnet, start_time)
             changed = True
 
         try:
@@ -394,6 +436,14 @@ def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
             changed = True
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
             module.fail_json_aws(e, msg="Couldn't associate ipv6 cidr {0} to {1}".format(ipv6_cidr, subnet['id']))
+        else:
+            if not check_mode and wait:
+                filters = ansible_dict_to_boto3_filter_list(
+                    {'ipv6-cidr-block-association.state': ['associated'],
+                     'vpc-id': subnet['vpc_id']}
+                )
+                handle_waiter(conn, module, 'subnet_exists',
+                              {'SubnetIds': [subnet['id']], 'Filters': filters}, start_time)
 
         if associate_resp.get('Ipv6CidrBlockAssociation', {}).get('AssociationId'):
             subnet['ipv6_association_id'] = associate_resp['Ipv6CidrBlockAssociation']['AssociationId']
@@ -422,9 +472,14 @@ def get_matching_subnet(conn, module, vpc_id, cidr):
 def ensure_subnet_present(conn, module):
     subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
     changed = False
+
+    # Initialize start so max time does not exceed the specified wait_timeout for multiple operations
+    start_time = time.time()
+
     if subnet is None:
         if not module.check_mode:
-            subnet = create_subnet(conn, module, module.params['vpc_id'], module.params['cidr'], ipv6_cidr=module.params['ipv6_cidr'], az=module.params['az'])
+            subnet = create_subnet(conn, module, module.params['vpc_id'], module.params['cidr'],
+                                   ipv6_cidr=module.params['ipv6_cidr'], az=module.params['az'], start_time=start_time)
         changed = True
         # Subnet will be None when check_mode is true
         if subnet is None:
@@ -432,24 +487,31 @@ def ensure_subnet_present(conn, module):
                 'changed': changed,
                 'subnet': {}
             }
+    if module.params['wait']:
+        handle_waiter(conn, module, 'subnet_exists', {'SubnetIds': [subnet['id']]}, start_time)
 
     if module.params['ipv6_cidr'] != subnet.get('ipv6_cidr_block'):
-        if ensure_ipv6_cidr_block(conn, module, subnet, module.params['ipv6_cidr'], module.check_mode):
+        if ensure_ipv6_cidr_block(conn, module, subnet, module.params['ipv6_cidr'], module.check_mode, start_time):
             changed = True
 
     if module.params['map_public'] != subnet['map_public_ip_on_launch']:
-        ensure_map_public(conn, module, subnet, module.params['map_public'], module.check_mode)
+        ensure_map_public(conn, module, subnet, module.params['map_public'], module.check_mode, start_time)
         changed = True
 
     if module.params['assign_instances_ipv6'] != subnet.get('assign_ipv6_address_on_creation'):
-        ensure_assign_ipv6_on_create(conn, module, subnet, module.params['assign_instances_ipv6'], module.check_mode)
+        ensure_assign_ipv6_on_create(conn, module, subnet, module.params['assign_instances_ipv6'], module.check_mode, start_time)
         changed = True
 
     if module.params['tags'] != subnet['tags']:
-        if ensure_tags(conn, module, subnet, module.params['tags'], module.params['purge_tags']):
+        stringified_tags_dict = dict((to_text(k), to_text(v)) for k, v in module.params['tags'].items())
+        if ensure_tags(conn, module, subnet, stringified_tags_dict, module.params['purge_tags'], start_time):
             changed = True
 
     subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
+    if not module.check_mode and module.params['wait']:
+        # GET calls are not monotonic for map_public_ip_on_launch and assign_ipv6_address_on_creation
+        # so we only wait for those if necessary just before returning the subnet
+        subnet = ensure_final_subnet(conn, module, subnet, start_time)
 
     return {
         'changed': changed,
@@ -457,6 +519,36 @@ def ensure_subnet_present(conn, module):
     }
 
 
+def ensure_final_subnet(conn, module, subnet, start_time):
+    for rewait in range(0, 30):
+        map_public_correct = False
+        assign_ipv6_correct = False
+
+        if module.params['map_public'] == subnet['map_public_ip_on_launch']:
+            map_public_correct = True
+        else:
+            if module.params['map_public']:
+                handle_waiter(conn, module, 'subnet_has_map_public', {'SubnetIds': [subnet['id']]}, start_time)
+            else:
+                handle_waiter(conn, module, 'subnet_no_map_public', {'SubnetIds': [subnet['id']]}, start_time)
+
+        if module.params['assign_instances_ipv6'] == subnet.get('assign_ipv6_address_on_creation'):
+            assign_ipv6_correct = True
+        else:
+            if module.params['assign_instances_ipv6']:
+                handle_waiter(conn, module, 'subnet_has_assign_ipv6', {'SubnetIds': [subnet['id']]}, start_time)
+            else:
+                handle_waiter(conn, module, 'subnet_no_assign_ipv6', {'SubnetIds': [subnet['id']]}, start_time)
+
+        if map_public_correct and assign_ipv6_correct:
+            break
+
+        time.sleep(5)
+        subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
+
+    return subnet
+
+
 def ensure_subnet_absent(conn, module):
     subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
     if subnet is None:
@@ -465,6 +557,8 @@ def ensure_subnet_absent(conn, module):
     try:
         if not module.check_mode:
             conn.delete_subnet(SubnetId=subnet['id'])
+            if module.params['wait']:
+                handle_waiter(conn, module, 'subnet_deleted', {'SubnetIds': [subnet['id']]}, time.time())
         return {'changed': True}
     except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
         module.fail_json_aws(e, msg="Couldn't delete subnet")
@@ -495,6 +589,9 @@ def main():
     if module.params.get('assign_instances_ipv6') and not module.params.get('ipv6_cidr'):
         module.fail_json(msg="assign_instances_ipv6 is True but ipv6_cidr is None or an empty string")
 
+    if LooseVersion(botocore.__version__) < "1.7.0":
+        module.warn("botocore >= 1.7.0 is required to use wait_timeout for custom wait times")
+
     region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
     connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
 
@@ -506,8 +603,7 @@ def main():
         elif state == 'absent':
             result = ensure_subnet_absent(connection, module)
     except botocore.exceptions.ClientError as e:
-        module.fail_json(msg=e.message, exception=traceback.format_exc(),
-                         **camel_dict_to_snake_dict(e.response))
+        module.fail_json_aws(e)
 
     module.exit_json(**result)
 


Test patch:
diff --git a/test/integration/targets/ec2_vpc_subnet/tasks/main.yml b/test/integration/targets/ec2_vpc_subnet/tasks/main.yml
--- a/test/integration/targets/ec2_vpc_subnet/tasks/main.yml
+++ b/test/integration/targets/ec2_vpc_subnet/tasks/main.yml
@@ -10,33 +10,55 @@
 
 - block:
 
+    - name: set up aws connection info
+      set_fact:
+        aws_connection_info: &aws_connection_info
+          aws_access_key: "{{ aws_access_key }}"
+          aws_secret_key: "{{ aws_secret_key }}"
+          security_token: "{{ security_token }}"
+          region: "{{ aws_region }}"
+      no_log: yes
+
     # ============================================================
     - name: create a VPC
       ec2_vpc_net:
         name: "{{ resource_prefix }}-vpc"
         state: present
         cidr_block: "10.232.232.128/26"
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         tags:
           Name: "{{ resource_prefix }}-vpc"
           Description: "Created by ansible-test"
       register: vpc_result
 
+    # ============================================================
+    - name: create subnet (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+        <<: *aws_connection_info
+        state: present
+      check_mode: true
+      register: vpc_subnet_create
+
+    - name: assert creation would happen
+      assert:
+        that:
+          - vpc_subnet_create.changed
+
     - name: create subnet (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
       register: vpc_subnet_create
 
@@ -47,19 +69,34 @@
            - 'vpc_subnet_create.subnet.id.startswith("subnet-")'
            - '"Name" in vpc_subnet_create.subnet.tags and vpc_subnet_create.subnet.tags["Name"] == ec2_vpc_subnet_name'
            - '"Description" in vpc_subnet_create.subnet.tags and vpc_subnet_create.subnet.tags["Description"] == ec2_vpc_subnet_description'
+    # ============================================================
+    - name: recreate subnet (expected changed=false) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+        <<: *aws_connection_info
+        state: present
+      check_mode: true
+      register: vpc_subnet_recreate
+
+    - name: assert recreation changed nothing (expected changed=false)
+      assert:
+        that:
+           - 'not vpc_subnet_recreate.changed'
 
     - name: recreate subnet (expected changed=false)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
       register: vpc_subnet_recreate
 
@@ -69,19 +106,56 @@
            - 'not vpc_subnet_recreate.changed'
            - 'vpc_subnet_recreate.subnet == vpc_subnet_create.subnet'
 
+    # ============================================================
+    - name: update subnet so instances launched in it are assigned an IP (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+        <<: *aws_connection_info
+        state: present
+        map_public: true
+      check_mode: true
+      register: vpc_subnet_modify
+
+    - name: assert subnet changed
+      assert:
+        that:
+          - vpc_subnet_modify.changed
+
+    - name: update subnet so instances launched in it are assigned an IP
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+        <<: *aws_connection_info
+        state: present
+        map_public: true
+      register: vpc_subnet_modify
+
+    - name: assert subnet changed
+      assert:
+        that:
+          - vpc_subnet_modify.changed
+          - vpc_subnet_modify.subnet.map_public_ip_on_launch
+
+    # ============================================================
     - name: add invalid ipv6 block to subnet (expected failed)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         ipv6_cidr: 2001:db8::/64
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
       register: vpc_subnet_ipv6_failed
       ignore_errors: yes
@@ -92,19 +166,36 @@
            - 'vpc_subnet_ipv6_failed.failed'
            - "'Couldn\\'t associate ipv6 cidr' in vpc_subnet_ipv6_failed.msg"
 
+    # ============================================================
+    - name: add a tag (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+          AnotherTag: SomeValue
+        <<: *aws_connection_info
+        state: present
+      check_mode: true
+      register: vpc_subnet_add_a_tag
+
+    - name: assert tag addition happened (expected changed=true)
+      assert:
+        that:
+           - 'vpc_subnet_add_a_tag.changed'
+
     - name: add a tag (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
           AnotherTag: SomeValue
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
       register: vpc_subnet_add_a_tag
 
@@ -116,17 +207,32 @@
            - '"Description" in vpc_subnet_add_a_tag.subnet.tags and vpc_subnet_add_a_tag.subnet.tags["Description"] == ec2_vpc_subnet_description'
            - '"AnotherTag" in vpc_subnet_add_a_tag.subnet.tags and vpc_subnet_add_a_tag.subnet.tags["AnotherTag"] == "SomeValue"'
 
+    # ============================================================
+    - name: remove tags with default purge_tags=true (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          AnotherTag: SomeValue
+        <<: *aws_connection_info
+        state: present
+      check_mode: true
+      register: vpc_subnet_remove_tags
+
+    - name: assert tag removal happened (expected changed=true)
+      assert:
+        that:
+           - 'vpc_subnet_remove_tags.changed'
+
     - name: remove tags with default purge_tags=true (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         tags:
           AnotherTag: SomeValue
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
       register: vpc_subnet_remove_tags
 
@@ -138,18 +244,35 @@
            - '"Description" not in vpc_subnet_remove_tags.subnet.tags'
            - '"AnotherTag" in vpc_subnet_remove_tags.subnet.tags and vpc_subnet_remove_tags.subnet.tags["AnotherTag"] == "SomeValue"'
 
+    # ============================================================
+    - name: change tags with purge_tags=false (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        az: "{{ aws_region }}a"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+        <<: *aws_connection_info
+        state: present
+        purge_tags: false
+      check_mode: true
+      register: vpc_subnet_change_tags
+
+    - name: assert tag addition happened (expected changed=true)
+      assert:
+        that:
+           - 'vpc_subnet_change_tags.changed'
+
     - name: change tags with purge_tags=false (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
-        az: "{{ ec2_region }}a"
+        az: "{{ aws_region }}a"
         vpc_id: "{{ vpc_result.vpc.id }}"
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
         purge_tags: false
       register: vpc_subnet_change_tags
@@ -162,15 +285,27 @@
            - '"Description" in vpc_subnet_change_tags.subnet.tags and vpc_subnet_change_tags.subnet.tags["Description"] == ec2_vpc_subnet_description'
            - '"AnotherTag" in vpc_subnet_change_tags.subnet.tags and vpc_subnet_change_tags.subnet.tags["AnotherTag"] == "SomeValue"'
 
+    # ============================================================
+    - name: test state=absent (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: absent
+        <<: *aws_connection_info
+      check_mode: true
+      register: result
+
+    - name: assert state=absent (expected changed=true)
+      assert:
+        that:
+           - 'result.changed'
+
     - name: test state=absent (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         state: absent
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
       register: result
 
     - name: assert state=absent (expected changed=true)
@@ -178,15 +313,55 @@
         that:
            - 'result.changed'
 
+    # ============================================================
+    - name: test state=absent (expected changed=false) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: absent
+        <<: *aws_connection_info
+      check_mode: true
+      register: result
+
+    - name: assert state=absent (expected changed=false)
+      assert:
+        that:
+           - 'not result.changed'
+
+    - name: test state=absent (expected changed=false)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: absent
+        <<: *aws_connection_info
+      register: result
+
+    - name: assert state=absent (expected changed=false)
+      assert:
+        that:
+           - 'not result.changed'
+
+    # ============================================================
+    - name: create subnet without AZ (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: present
+        <<: *aws_connection_info
+      check_mode: true
+      register: subnet_without_az
+
+    - name: check that subnet without AZ works fine
+      assert:
+        that:
+           - 'subnet_without_az.changed'
+
     - name: create subnet without AZ
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         state: present
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
       register: subnet_without_az
 
     - name: check that subnet without AZ works fine
@@ -194,15 +369,27 @@
         that:
            - 'subnet_without_az.changed'
 
+    # ============================================================
+    - name: remove subnet without AZ (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: absent
+        <<: *aws_connection_info
+      check_mode: true
+      register: result
+
+    - name: assert state=absent (expected changed=true)
+      assert:
+        that:
+           - 'result.changed'
+
     - name: remove subnet without AZ
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         state: absent
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
       register: result
 
     - name: assert state=absent (expected changed=true)
@@ -210,6 +397,7 @@
         that:
            - 'result.changed'
 
+    # ============================================================
     # FIXME - Replace by creating IPv6 enabled VPC once ec2_vpc_net module supports it.
     - name: install aws cli - FIXME temporary this should go for a lighterweight solution
       command: pip install awscli
@@ -220,7 +408,10 @@
           AWS_ACCESS_KEY_ID: '{{aws_access_key}}'
           AWS_SECRET_ACCESS_KEY: '{{aws_secret_key}}'
           AWS_SESSION_TOKEN: '{{security_token}}'
-          AWS_DEFAULT_REGION: '{{ec2_region}}'
+          AWS_DEFAULT_REGION: '{{aws_region}}'
+
+    - name: wait for the IPv6 CIDR to be assigned
+      command: sleep 5
 
     - name: Get the assigned IPv6 CIDR
       command: aws ec2 describe-vpcs --vpc-ids '{{ vpc_result.vpc.id }}'
@@ -228,12 +419,32 @@
           AWS_ACCESS_KEY_ID: '{{aws_access_key}}'
           AWS_SECRET_ACCESS_KEY: '{{aws_secret_key}}'
           AWS_SESSION_TOKEN: '{{security_token}}'
-          AWS_DEFAULT_REGION: '{{ec2_region}}'
+          AWS_DEFAULT_REGION: '{{aws_region}}'
       register: vpc_ipv6
 
     - set_fact:
         vpc_ipv6_cidr: "{{ vpc_ipv6.stdout | from_json | json_query('Vpcs[0].Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock') }}"
 
+    # ============================================================
+    - name: create subnet with IPv6 (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
+        assign_instances_ipv6: true
+        state: present
+        <<: *aws_connection_info
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+      check_mode: true
+      register: vpc_subnet_ipv6_create
+
+    - name: assert creation with IPv6 happened (expected changed=true)
+      assert:
+        that:
+           - 'vpc_subnet_ipv6_create.changed'
+
     - name: create subnet with IPv6 (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
@@ -241,10 +452,7 @@
         ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
         assign_instances_ipv6: true
         state: present
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
           Description: '{{ec2_vpc_subnet_description}}'
@@ -260,16 +468,33 @@
            - '"Description" in vpc_subnet_ipv6_create.subnet.tags and vpc_subnet_ipv6_create.subnet.tags["Description"] == ec2_vpc_subnet_description'
            - 'vpc_subnet_ipv6_create.subnet.assign_ipv6_address_on_creation'
 
+    # ============================================================
+    - name: recreate subnet (expected changed=false) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
+        assign_instances_ipv6: true
+        <<: *aws_connection_info
+        state: present
+        tags:
+          Name: '{{ec2_vpc_subnet_name}}'
+          Description: '{{ec2_vpc_subnet_description}}'
+      check_mode: true
+      register: vpc_subnet_ipv6_recreate
+
+    - name: assert recreation changed nothing (expected changed=false)
+      assert:
+        that:
+           - 'not vpc_subnet_ipv6_recreate.changed'
+
     - name: recreate subnet (expected changed=false)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
         assign_instances_ipv6: true
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
         tags:
           Name: '{{ec2_vpc_subnet_name}}'
@@ -282,16 +507,31 @@
            - 'not vpc_subnet_ipv6_recreate.changed'
            - 'vpc_subnet_ipv6_recreate.subnet == vpc_subnet_ipv6_create.subnet'
 
+    # ============================================================
+    - name: change subnet ipv6 attribute (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
+        assign_instances_ipv6: false
+        <<: *aws_connection_info
+        state: present
+        purge_tags: false
+      check_mode: true
+      register: vpc_change_attribute
+
+    - name: assert assign_instances_ipv6 attribute changed (expected changed=true)
+      assert:
+        that:
+           - 'vpc_change_attribute.changed'
+
     - name: change subnet ipv6 attribute (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
         assign_instances_ipv6: false
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
         purge_tags: false
       register: vpc_change_attribute
@@ -302,15 +542,13 @@
            - 'vpc_change_attribute.changed'
            - 'not vpc_change_attribute.subnet.assign_ipv6_address_on_creation'
 
+    # ============================================================
     - name: add second subnet with duplicate ipv6 cidr (expected failure)
       ec2_vpc_subnet:
         cidr: "10.232.232.144/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
         purge_tags: false
       register: vpc_add_duplicate_ipv6
@@ -322,14 +560,27 @@
            - 'vpc_add_duplicate_ipv6.failed'
            - "'The IPv6 CIDR \\'{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}\\' conflicts with another subnet' in vpc_add_duplicate_ipv6.msg"
 
+    # ============================================================
+    - name: remove subnet ipv6 cidr (expected changed=true) (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        <<: *aws_connection_info
+        state: present
+        purge_tags: false
+      check_mode: true
+      register: vpc_remove_ipv6_cidr
+
+    - name: assert subnet ipv6 cidr removed (expected changed=true)
+      assert:
+        that:
+           - 'vpc_remove_ipv6_cidr.changed'
+
     - name: remove subnet ipv6 cidr (expected changed=true)
       ec2_vpc_subnet:
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
-        region: '{{ec2_region}}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
         state: present
         purge_tags: false
       register: vpc_remove_ipv6_cidr
@@ -341,6 +592,75 @@
            - "vpc_remove_ipv6_cidr.subnet.ipv6_cidr_block == ''"
            - 'not vpc_remove_ipv6_cidr.subnet.assign_ipv6_address_on_creation'
 
+    # ============================================================
+    - name: test adding a tag that looks like a boolean to the subnet (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: present
+        purge_tags: false
+        tags:
+          looks_like_boolean: true
+        <<: *aws_connection_info
+      check_mode: true
+      register: vpc_subnet_info
+
+    - name: assert a tag was added
+      assert:
+        that:
+          - 'vpc_subnet_info.changed'
+
+    - name: test adding a tag that looks like a boolean to the subnet
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: present
+        purge_tags: false
+        tags:
+          looks_like_boolean: true
+        <<: *aws_connection_info
+      register: vpc_subnet_info
+
+    - name: assert a tag was added
+      assert:
+        that:
+          - 'vpc_subnet_info.changed'
+          - 'vpc_subnet_info.subnet.tags.looks_like_boolean == "True"'
+
+    # ============================================================
+    - name: test idempotence adding a tag that looks like a boolean (CHECK MODE)
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: present
+        purge_tags: false
+        tags:
+          looks_like_boolean: true
+        <<: *aws_connection_info
+      check_mode: true
+      register: vpc_subnet_info
+
+    - name: assert a tag was added
+      assert:
+        that:
+          - 'not vpc_subnet_info.changed'
+
+    - name: test idempotence adding a tag that looks like a boolean
+      ec2_vpc_subnet:
+        cidr: "10.232.232.128/28"
+        vpc_id: "{{ vpc_result.vpc.id }}"
+        state: present
+        purge_tags: false
+        tags:
+          looks_like_boolean: true
+        <<: *aws_connection_info
+      register: vpc_subnet_info
+
+    - name: assert a tag was added
+      assert:
+        that:
+          - 'not vpc_subnet_info.changed'
+
   always:
 
     ################################################
@@ -352,17 +672,11 @@
         cidr: "10.232.232.128/28"
         vpc_id: "{{ vpc_result.vpc.id }}"
         state: absent
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info
 
     - name: tidy up VPC
       ec2_vpc_net:
         name: "{{ resource_prefix }}-vpc"
         state: absent
         cidr_block: "10.232.232.128/26"
-        region: '{{ ec2_region }}'
-        aws_access_key: '{{ aws_access_key }}'
-        aws_secret_key: '{{ aws_secret_key }}'
-        security_token: '{{ security_token }}'
+        <<: *aws_connection_info