Repository: ansible/ansible

Issue:
elb_network_lb doesn't work correctly with subnet_mappings, preventing EIP support from working
<!---
Verify first that your issue/request is not already reported on GitHub.
THIS FORM WILL BE READ BY A MACHINE, COMPLETE ALL SECTIONS AS DESCRIBED.
Also test if the latest release, and devel branch are affected too.
ALWAYS add information AFTER (OUTSIDE) these html comments.
Otherwise it may end up being automatically closed by our bot. -->

##### SUMMARY
The `elb_network_lb` module documentation shows support for using either `subnets` which is a list of subnets, or `subnet_mappings` which is a list of dicts containing a `SubnetId` key, and optionally an `AllocationId` key to specify an EIP to use for that subnet.

Trying to use subnet mappings on a new NLB fails saying that subnets is a required parameter.

If the NLB exists and is in the desired state, subnet mappings works, but if the mappings contain allocation IDs, they are ignored.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
`lib/ansible/module_utils/aws/elbv2.py`

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.1
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/usr/share/ansible']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.12 (default, Dec  4 2017, 14:50:18) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->
`DEFAULT_FORKS(/etc/ansible/ansible.cfg) = 15`

##### OS / ENVIRONMENT
<!--- Mention, BELOW THIS COMMENT, the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.-->
N/A

##### STEPS TO REPRODUCE
<!--- For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used. -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
  - elb_network_lb:
      subnet_mappings:
        - SubnetId: subnet-1234abcd
          AllocationId: eipalloc-abcd1234
        - SubnetId: subnet-5678efab
          AllocationId: eipalloc-efab-5678
    delegate_to: localhost
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Create or validate an NLB with EIPs attached

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
An error occurred (ValidationError) when calling the CreateLoadBalancer operation: At least one subnet must be specified
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/module_utils/aws/elbv2.py b/lib/ansible/module_utils/aws/elbv2.py
--- a/lib/ansible/module_utils/aws/elbv2.py
+++ b/lib/ansible/module_utils/aws/elbv2.py
@@ -152,27 +152,31 @@ def compare_subnets(self):
         :return: bool True if they match otherwise False
         """
 
-        subnet_id_list = []
-        subnets = []
+        subnet_mapping_id_list = []
+        subnet_mappings = []
 
         # Check if we're dealing with subnets or subnet_mappings
         if self.subnets is not None:
-            # We need to first get the subnet ID from the list
-            subnets = self.subnets
+            # Convert subnets to subnet_mappings format for comparison
+            for subnet in self.subnets:
+                subnet_mappings.append({'SubnetId': subnet})
 
         if self.subnet_mappings is not None:
-            # Make a list from the subnet_mappings dict
-            subnets_from_mappings = []
-            for subnet_mapping in self.subnet_mappings:
-                subnets.append(subnet_mapping['SubnetId'])
+            # Use this directly since we're comparing as a mapping
+            subnet_mappings = self.subnet_mappings
 
+        # Build a subnet_mapping style struture of what's currently
+        # on the load balancer
         for subnet in self.elb['AvailabilityZones']:
-            subnet_id_list.append(subnet['SubnetId'])
+            this_mapping = {'SubnetId': subnet['SubnetId']}
+            for address in subnet['LoadBalancerAddresses']:
+                if 'AllocationId' in address:
+                    this_mapping['AllocationId'] = address['AllocationId']
+                    break
 
-        if set(subnet_id_list) != set(subnets):
-            return False
-        else:
-            return True
+            subnet_mapping_id_list.append(this_mapping)
+
+        return set(frozenset(mapping.items()) for mapping in subnet_mapping_id_list) == set(frozenset(mapping.items()) for mapping in subnet_mappings)
 
     def modify_subnets(self):
         """
@@ -359,6 +363,8 @@ def create_elb(self):
         # Other parameters
         if self.subnets is not None:
             params['Subnets'] = self.subnets
+        if self.subnet_mappings is not None:
+            params['SubnetMappings'] = self.subnet_mappings
         params['Scheme'] = self.scheme
         if self.tags:
             params['Tags'] = self.tags
@@ -400,6 +406,14 @@ def modify_elb_attributes(self):
                     AWSRetry.jittered_backoff()(self.connection.delete_load_balancer)(LoadBalancerArn=self.elb['LoadBalancerArn'])
                 self.module.fail_json_aws(e)
 
+    def modify_subnets(self):
+        """
+        Modify elb subnets to match module parameters (unsupported for NLB)
+        :return:
+        """
+
+        self.module.fail_json(msg='Modifying subnets and elastic IPs is not supported for Network Load Balancer')
+
 
 class ELBListeners(object):
 


Test patch: