Repository: ansible/ansible

Issue:
Support SKU for azure loadbalancer
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments. -->

The loadbalancer should support the SKU option with choices `basic` and `standard` from the latest API version

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Feature Idea

##### COMPONENT NAME
<!--- Insert, BELOW THIS COMMENT, the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path-->

azure_rm_loadbalancer

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
2.5.0
```

##### 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).-->

##### 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.-->

##### SUMMARY
<!--- Explain the problem briefly -->

##### 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 -->
```

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/module_utils/azure_rm_common.py b/lib/ansible/module_utils/azure_rm_common.py
--- a/lib/ansible/module_utils/azure_rm_common.py
+++ b/lib/ansible/module_utils/azure_rm_common.py
@@ -926,13 +926,13 @@ def network_client(self):
         if not self._network_client:
             self._network_client = self.get_mgmt_svc_client(NetworkManagementClient,
                                                             base_url=self._cloud_environment.endpoints.resource_manager,
-                                                            api_version='2017-06-01')
+                                                            api_version='2017-11-01')
         return self._network_client
 
     @property
     def network_models(self):
         self.log("Getting network models...")
-        return NetworkManagementClient.models("2017-06-01")
+        return NetworkManagementClient.models("2017-11-01")
 
     @property
     def rm_client(self):
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py b/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py
@@ -43,6 +43,13 @@
         description:
             - Valid azure location. Defaults to location of the resource group.
         default: resource_group location
+    sku:
+        description:
+            The load balancer SKU.
+        choices:
+            - Basic
+            - Standard
+        version_added: 2.6
     frontend_ip_configurations:
         description: List of frontend IPs to be used
         suboptions:
@@ -51,7 +58,20 @@
                 required: True
             public_ip_address:
                 description: Name of an existing public IP address object in the current resource group to associate with the security group.
-                required: True
+            private_ip_address:
+                description: The reference of the Public IP resource.
+                version_added: 2.6
+            private_ip_allocation_method:
+                description: The Private IP allocation method.
+                choices:
+                    - Static
+                    - Dynamic
+                version_added: 2.6
+            subnet:
+                description:
+                    - The reference of the subnet resource.
+                    - Should be an existing subnet's resource id.
+                version_added: 2.6
         version_added: 2.5
     backend_address_pools:
         description: List of backend address pools
@@ -321,7 +341,7 @@
 '''
 
 import random
-from ansible.module_utils.azure_rm_common import AzureRMModuleBase
+from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
 
 try:
     from msrestazure.tools import parse_resource_id
@@ -337,8 +357,16 @@
         required=True
     ),
     public_ip_address=dict(
-        type='str',
-        required=True
+        type='str'
+    ),
+    private_ip_address=dict(
+        type='str'
+    ),
+    private_ip_allocation_method=dict(
+        type='str'
+    ),
+    subnet=dict(
+        type='str'
     )
 )
 
@@ -474,6 +502,10 @@ def __init__(self):
             location=dict(
                 type='str'
             ),
+            sku=dict(
+                type='str',
+                choices=['Basic', 'Standard']
+            ),
             frontend_ip_configurations=dict(
                 type='list',
                 elements='dict',
@@ -556,6 +588,7 @@ def __init__(self):
         self.resource_group = None
         self.name = None
         self.location = None
+        self.sku = None
         self.frontend_ip_configurations = None
         self.backend_address_pools = None
         self.probes = None
@@ -658,7 +691,10 @@ def exec_module(self, **kwargs):
             # create or update
             frontend_ip_configurations_param = [self.network_models.FrontendIPConfiguration(
                 name=item.get('name'),
-                public_ip_address=self.get_public_ip_address(item.get('public_ip_address'))
+                public_ip_address=self.get_public_ip_address_instance(item.get('public_ip_address')) if item.get('public_ip_address') else None,
+                private_ip_address=item.get('private_ip_address'),
+                private_ip_allocation_method=item.get('private_ip_allocation_method'),
+                subnet=self.network_models.Subnet(id=item.get('subnet')) if item.get('subnet') else None
             ) for item in self.frontend_ip_configurations] if self.frontend_ip_configurations else None
 
             backend_address_pools_param = [self.network_models.BackendAddressPool(
@@ -723,6 +759,7 @@ def exec_module(self, **kwargs):
             ) for item in self.load_balancing_rules] if self.load_balancing_rules else None
 
             param = self.network_models.LoadBalancer(
+                sku=self.network_models.LoadBalancerSku(self.sku) if self.sku else None,
                 location=self.location,
                 frontend_ip_configurations=frontend_ip_configurations_param,
                 backend_address_pools=backend_address_pools_param,
@@ -738,16 +775,11 @@ def exec_module(self, **kwargs):
 
         return self.results
 
-    def get_public_ip_address(self, id):
+    def get_public_ip_address_instance(self, id):
         """Get a reference to the public ip address resource"""
         self.log('Fetching public ip address {}'.format(id))
-        pip_dict = parse_resource_id(id)
-        resource_group = pip_dict.get('resource_group', self.resource_group)
-        name = pip_dict.get('name')
-        try:
-            return self.network_client.public_ip_addresses.get(resource_group, name)
-        except CloudError as err:
-            self.fail('Error fetching public ip address {} - {}'.format(name, str(err)))
+        resource_id = format_resource_id(id, self.subscription_id, 'Microsoft.Network', 'publicIPAddresses', self.resource_group)
+        return self.network_models.PublicIPAddress(id=resource_id)
 
     def get_load_balancer(self):
         """Get a load balancer"""
@@ -784,6 +816,7 @@ def load_balancer_to_dict(load_balancer):
         id=load_balancer.id,
         name=load_balancer.name,
         location=load_balancer.location,
+        sku=load_balancer.sku.name,
         tags=load_balancer.tags,
         provisioning_state=load_balancer.provisioning_state,
         etag=load_balancer.etag,
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py b/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_publicipaddress.py
@@ -61,6 +61,13 @@
         description:
             - Valid azure location. Defaults to location of the resource group.
         default: resource_group location
+    sku:
+        description:
+            - The public IP address SKU.
+        choices:
+            - Basic
+            - Standard
+        version_added: 2.6
 
 extends_documentation_fragment:
     - azure
@@ -125,7 +132,8 @@ def pip_to_dict(pip):
         ip_address=pip.ip_address,
         idle_timeout_in_minutes=pip.idle_timeout_in_minutes,
         provisioning_state=pip.provisioning_state,
-        etag=pip.etag
+        etag=pip.etag,
+        sku=pip.sku.name
     )
     if pip.dns_settings:
         result['dns_settings']['domain_name_label'] = pip.dns_settings.domain_name_label
@@ -145,6 +153,7 @@ def __init__(self):
             location=dict(type='str'),
             allocation_method=dict(type='str', default='Dynamic', choices=['Dynamic', 'Static']),
             domain_name=dict(type='str', aliases=['domain_name_label']),
+            sku=dict(type='str', choices=['Basic', 'Standard'])
         )
 
         self.resource_group = None
@@ -154,6 +163,7 @@ def __init__(self):
         self.tags = None
         self.allocation_method = None
         self.domain_name = None
+        self.sku = None
 
         self.results = dict(
             changed=False,
@@ -194,6 +204,11 @@ def exec_module(self, **kwargs):
                     changed = True
                     results['public_ip_allocation_method'] = self.allocation_method
 
+                if self.sku and self.sku != results['sku']:
+                    self.log("CHANGED: sku")
+                    changed = True
+                    results['sku'] = self.sku
+
                 update_tags, results['tags'] = self.update_tags(results['tags'])
                 if update_tags:
                     changed = True
@@ -220,6 +235,7 @@ def exec_module(self, **kwargs):
                     pip = self.network_models.PublicIPAddress(
                         location=self.location,
                         public_ip_allocation_method=self.allocation_method,
+                        sku=self.network_models.PublicIPAddressSku(name=self.sku) if self.sku else None
                     )
                     if self.tags:
                         pip.tags = self.tags


Test patch:
diff --git a/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml b/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml
--- a/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml
+++ b/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml
@@ -1,3 +1,10 @@
+- name: create public ip
+  azure_rm_publicipaddress:
+    name: ansiblepipstandard
+    sku: Standard
+    allocation_method: Static
+    resource_group: '{{ resource_group }}'
+
 - name: create public ip
   azure_rm_publicipaddress:
     name: ansiblepip3
@@ -46,7 +53,8 @@
   azure_rm_loadbalancer:
     resource_group: '{{ resource_group }}'
     name: lbtestfromansible
-    public_ip_address: ansiblepip3
+    sku: Standard
+    public_ip_address: ansiblepipstandard
     probe_protocol: Tcp
     probe_port: 80
     probe_interval: 10
@@ -64,7 +72,9 @@
 
 - name: assert complex load balancer created
   assert:
-    that: output.changed
+    that:
+    - output.changed
+    - output.state.sku == 'Standard'
 
 - name: delete load balancer
   azure_rm_loadbalancer:
@@ -110,8 +120,78 @@
     name: lbtestfromansible
     state: absent
 
+- name: Create virtual network
+  azure_rm_virtualnetwork:
+      resource_group: "{{ resource_group }}"
+      name: lbtestfromansiblevn
+      address_prefixes: "10.10.0.0/16"
+
+- name: Add subnet
+  azure_rm_subnet:
+      resource_group: "{{ resource_group }}"
+      name: lbtestfromansiblesb
+      address_prefix: "10.10.0.0/24"
+      virtual_network: lbtestfromansiblevn
+  register: subnet
+
+- name: create internal loadbalancer
+  azure_rm_loadbalancer:
+    resource_group: '{{ resource_group }}'
+    name: lbtestfromansible
+    frontend_ip_configurations:
+      - name: frontendipconf0
+        private_ip_address: 10.10.0.10
+        private_ip_allocation_method: Static
+        subnet: "{{ subnet.state.id }}"
+    backend_address_pools:
+      - name: backendaddrpool0
+    probes:
+      - name: prob0
+        port: 80
+    inbound_nat_pools:
+      - name: inboundnatpool0
+        frontend_ip_configuration_name: frontendipconf0
+        protocol: Tcp
+        frontend_port_range_start: 80
+        frontend_port_range_end: 81
+        backend_port: 8080
+    load_balancing_rules:
+      - name: lbrbalancingrule0
+        frontend_ip_configuration: frontendipconf0
+        backend_address_pool: backendaddrpool0
+        frontend_port: 80
+        backend_port: 80
+        probe: prob0
+  register: output
+
+- name: assert complex load balancer created
+  assert:
+    that: output.changed
+
+- name: delete load balancer
+  azure_rm_loadbalancer:
+    resource_group: '{{ resource_group }}'
+    name: lbtestfromansible
+    state: absent
+
 - name: cleanup public ip
   azure_rm_publicipaddress:
-    name: ansiblepip3
+    name: "{{ item }}"
     resource_group: '{{ resource_group }}'
     state: absent
+  with_items:
+  - ansiblepip3
+  - ansiblepipstandard
+
+- name: cleanup subnet
+  azure_rm_subnet:
+      resource_group: "{{ resource_group }}"
+      name: lbtestfromansiblesb
+      virtual_network: lbtestfromansiblevn
+      state: absent
+
+- name: cleanup virtual network
+  azure_rm_virtualnetwork:
+      resource_group: "{{ resource_group }}"
+      name: lbtestfromansiblevn
+      state: absent