Repository: ansible/ansible
Pull request: Fix #33114: vmss has parameter load balancer
##### SUMMARY
<!--- Describe the change, including rationale and design decisions -->

Fix #33114 

<!---
If you are fixing an existing issue, please include "Fixes #nnn" in your
commit message and your description; but you should still explain what
the change does.
-->

Azure virtual machine scale set can have load balancer parameter, so that it can expose a public ip address

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Pull Request

##### COMPONENT NAME
<!--- Name of the module/plugin/module/task -->
azure_rm_virtualmachine_scaleset

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
2.4
```


##### ADDITIONAL INFORMATION
<!---
Include additional information to help people understand the change here.
For bugs that don't have a linked bug report, a step-by-step reproduction
of the problem is helpful.
  -->

<!--- Paste verbatim command output below, e.g. before and after your change -->
```

```


Problem statement:
azure_rm_virtualmachine_scaleset should support Azure Azure Load Balancer with inbound NAT rules
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
azure_rm_virtualmachine_scaleset

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0
  config file = None
  configured module search path = [u'/Users/zhijzhao/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.14 (default, Sep 25 2017, 09:53:22) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)]

```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste 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).

-->
No change at all
##### OS / ENVIRONMENT
<!---
Mention 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.
-->
macOS 10.13.1
##### SUMMARY
<!--- Explain the problem briefly -->
Right now, only subnet is passed to VirtualMachineScaleSetIPConfiguration in the module implementation. In other words, user can not access the provisioned vmss with public ip address. 

Actually, the API of VirtualMachineScaleSetIPConfiguration support load balancer. 

My understanding is that vmss with load balancer is the most common scenario, which is also the default behavior in Azure Portal. 

##### 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

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```

Hints from issue discussion:
@yaweiw @zikalino 
cc @sozercan
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Commit message: fix indent

Patch to review:
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py
index 776c7340dd8136..558dca77985522 100644
--- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py
@@ -156,6 +156,10 @@
             - Subnet name
         aliases:
             - subnet
+    load_balancer:
+        description:
+            - Load balancer name
+        version_added: "2.5"
     remove_on_absent:
         description:
             - When removing a VM using state 'absent', also remove associated resources
@@ -296,6 +300,7 @@
 
 try:
     from msrestazure.azure_exceptions import CloudError
+    from msrestazure.tools import parse_resource_id
     from azure.mgmt.compute.models import VirtualMachineScaleSet, \
         VirtualMachineScaleSetStorageProfile, \
         VirtualMachineScaleSetOSProfile, \
@@ -309,7 +314,7 @@
         VirtualMachineScaleSetPublicIPAddressConfigurationDnsSettings, \
         VirtualMachineScaleSetPublicIPAddressConfiguration, Sku, \
         UpgradePolicy, VirtualMachineScaleSetNetworkConfiguration, \
-        ApiEntityReference, ImageReference
+        ApiEntityReference, ImageReference, SubResource
 
     from azure.mgmt.network.models import PublicIPAddress, \
         NetworkSecurityGroup, NetworkInterface, \
@@ -352,6 +357,7 @@ def __init__(self):
             managed_disk_type=dict(type='str', choices=['Standard_LRS', 'Premium_LRS']),
             data_disks=dict(type='list'),
             subnet_name=dict(type='str', aliases=['subnet']),
+            load_balancer=dict(type='str'),
             virtual_network_name=dict(type='str', aliases=['virtual_network']),
             remove_on_absent=dict(type='list', default=['all']),
         )
@@ -378,6 +384,7 @@ def __init__(self):
         self.virtual_network_name = None
         self.tags = None
         self.differences = None
+        self.load_balancer = None
 
         self.results = dict(
             changed=False,
@@ -511,12 +518,24 @@ def exec_module(self, **kwargs):
                         if disable_ssh_password and not self.ssh_public_keys:
                             self.fail("Parameter error: ssh_public_keys required when disabling SSH password.")
 
-                    if self.subnet_name:
-                        subnet = self.get_subnet(self.virtual_network_name, self.subnet_name)
-
                     if not self.virtual_network_name:
                         default_vnet = self.create_default_vnet()
                         virtual_network = default_vnet.id
+                        self.virtual_network_name = default_vnet.name
+
+                    if self.subnet_name:
+                        subnet = self.get_subnet(self.virtual_network_name, self.subnet_name)
+
+                    load_balancer_backend_address_pools = None
+                    load_balancer_inbound_nat_pools = None
+                    if self.load_balancer:
+                        load_balancer = self.get_load_balancer(self.load_balancer)
+                        load_balancer_backend_address_pools = ([SubResource(resource.id)
+                                                                for resource in load_balancer.backend_address_pools]
+                                                               if load_balancer.backend_address_pools else None)
+                        load_balancer_inbound_nat_pools = ([SubResource(resource.id)
+                                                            for resource in load_balancer.inbound_nat_pools]
+                                                           if load_balancer.inbound_nat_pools else None)
 
                     if not self.short_hostname:
                         self.short_hostname = self.name
@@ -562,7 +581,10 @@ def exec_module(self, **kwargs):
                                                 name='default',
                                                 subnet=ApiEntityReference(
                                                     id=subnet.id
-                                                )
+                                                ),
+                                                primary=True,
+                                                load_balancer_backend_address_pools=load_balancer_backend_address_pools,
+                                                load_balancer_inbound_nat_pools=load_balancer_inbound_nat_pools
                                             )
                                         ]
                                     )
@@ -678,6 +700,13 @@ def get_subnet(self, vnet_name, subnet_name):
                 str(exc)))
         return subnet
 
+    def get_load_balancer(self, id):
+        id_dict = parse_resource_id(id)
+        try:
+            return self.network_client.load_balancers.get(id_dict.get('resource_group', self.resource_group), id_dict.get('name'))
+        except Exception as exc:
+            self.fail("Error fetching load balancer {0} - {1}".format(id, str(exc)))
+
     def serialize_vmss(self, vmss):
         '''
         Convert a VirtualMachineScaleSet object to dict.
diff --git a/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml b/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml
index 586cbdb7192902..7ed76d6b93ed83 100644
--- a/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml
+++ b/test/integration/targets/azure_rm_virtualmachine_scaleset/tasks/main.yml
@@ -14,9 +14,15 @@
 - name: Create public IP address
   azure_rm_publicipaddress:
     resource_group: "{{ resource_group }}"
-    allocation_method: Dynamic
+    allocation_method: Static
     name: testPublicIP
 
+- name: Create load balancer
+  azure_rm_loadbalancer:
+    resource_group: "{{ resource_group }}"
+    name: testLB
+    public_ip_address_name: testPublicIP
+
 - name: Create VMSS
   azure_rm_virtualmachine_scaleset:
     resource_group: "{{ resource_group }}"
@@ -28,6 +34,7 @@
     capacity: 2
     virtual_network_name: testVnet
     subnet_name: testSubnet
+    load_balancer: testLB
     upgrade_policy: Manual
     tier: Standard
     managed_disk_type: Standard_LRS
@@ -73,10 +80,15 @@
         caching: ReadWrite
         managed_disk_type: Standard_LRS
 
+- name: Delete load balancer
+  azure_rm_loadbalancer:
+    resource_group: "{{ resource_group }}"
+    name: testLB
+    state: absent
+
 - name: Delete public IP address
   azure_rm_publicipaddress:
     resource_group: "{{ resource_group }}"
-    allocation_method: Dynamic
     state: absent
     name: testPublicIP
 


Code review comments:
diff_hunk: @@ -678,6 +700,13 @@ def get_subnet(self, vnet_name, subnet_name):
                 str(exc)))
         return subnet
 
+    def get_load_balancer(self, id):
+        id_dict = parse_resource_id(id)
+        try:
+            return self.network_client.load_balancers.get(id_dict.get('resource_group', self.resource_group), id_dict.get('name'))
+        except Exception as exc:
path: lib/ansible/modules/cloud/azure/azure_rm_virtualmachine_scaleset.py
text: @user1:
We try to avoid generic `Exception` and use specific ones like `CloudError`