Repository: ansible/ansible

Issue:
azure_rm_virtualmachine not create VM with multi NICs
<!---
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: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
azure_rm_virtualmachine

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
2.3.1.0
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
Nope

##### 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.
-->
Linux Ubuntu 16.10 Yaketty
Linux Debian 8 Jessy

##### SUMMARY
<!--- Explain the problem briefly -->
Is not possible to create a VM with more than one NIC.
The ```azure_rm_virtualmachine``` module return error.

##### 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.
-->

1. Getting Started with Azure
Follow this page https://docs.ansible.com/ansible/guide_azure.html

2. Run this playbook 
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create Azure VM
  hosts: localhost
  connection: local
  tasks:
    - name: Create storage account
      azure_rm_storageaccount:
        resource_group: RGtest2
        name: 'jonstorage01'
        account_type: Standard_LRS
    
    - name: Create virtual network
      azure_rm_virtualnetwork:
        resource_group: RGtest2
        name: 'jon-vn'
        address_prefixes: "10.1.0.0/16"
    
    - name: Add subnet
      azure_rm_subnet:
        resource_group: RGtest2
        name: 'jon-subnet1'
        address_prefix: "10.1.0.0/24"
        virtual_network: 'jon-vn'

    - name: Add subnet2
      azure_rm_subnet:
        resource_group: RGtest2
        name: 'jon-subnet2'
        address_prefix: "10.1.1.0/24"
        virtual_network: 'jon-vn'

    - name: Create public ip
      azure_rm_publicipaddress:
        resource_group: RGtest2
        allocation_method: Static
        name: 'jon-public-ip'
    
    - name: Create security group that allows SSH
      azure_rm_securitygroup:
        resource_group: RGtest2
        name: 'jon-securitygroup-ssh'
        rules:
          - name: SSH
            protocol: Tcp
            destination_port_range: 22
            access: Allow
            priority: 101
            direction: Inbound

    - name: Create security group all denied
      azure_rm_securitygroup:
        resource_group: RGtest2
        name: 'jon-securitygroup-alldenied'

    - name: Create NIC
      azure_rm_networkinterface:
        resource_group: RGtest2
        name: 'jon-nic1'
        virtual_network: 'jon-vn'
        subnet: 'jon-subnet1'
        public_ip_name: 'jon-public-ip'
        security_group: 'jon-securitygroup-ssh'

    - name: Create NIC02
      azure_rm_networkinterface:
        resource_group: RGtest2
        name: 'jon-nic2'
        virtual_network: 'jon-vn'
        subnet: 'jon-subnet2'
        public_ip: no
        security_group: 'jon-securitygroup-alldenied'

    - name: Create virtual machine
      azure_rm_virtualmachine:
        resource_group: RGtest2
        name: 'jon-vm'
        vm_size: Standard_D1
        storage_account: 'jonstorage01'
        storage_container: 'jon-vm'
        storage_blob: 'jon-vm01.vhd'
        admin_username: Ansible
        admin_password: Password99!
        network_interfaces: [ 'jon-nic1', 'jon-nic2' ]
        image:
          offer: Debian
          publisher: credativ
          sku: '8'
          version: latest
```

<!--- 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 VM.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Error, the VM is not created.
Problem with Primary property for network interfaces.

```
TASK [Create virtual machine] ********************************************************************************************************************************
task path: /home/jon/test/azure/azure.yml:88
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: jon
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "ad_user": null, 
            "admin_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "admin_username": "Ansible", 
            "allocated": true, 
            "append_tags": true, 
            "client_id": null, 
            "image": {
                "offer": "Debian", 
                "publisher": "credativ", 
                "sku": "8", 
                "version": "8.0.201706210"
            }, 
            "location": null, 
            "name": "jon-vm", 
            "network_interface_names": [
                "jon-nic1", 
                "jon-nic2"
            ], 
            "network_interfaces": [
                "jon-nic1", 
                "jon-nic2"
            ], 
            "open_ports": null, 
            "os_disk_caching": "ReadOnly", 
            "os_type": "Linux", 
            "password": null, 
            "profile": null, 
            "public_ip_allocation_method": "Static", 
            "remove_on_absent": [
                "all"
            ], 
            "resource_group": "RGtest2", 
            "restarted": false, 
            "secret": null, 
            "short_hostname": null, 
            "ssh_password_enabled": true, 
            "ssh_public_keys": null, 
            "started": true, 
            "state": "present", 
            "storage_account": "jonstorage01", 
            "storage_account_name": "jonstorage01", 
            "storage_blob": "jon-vm01.vhd", 
            "storage_blob_name": "jon-vm01.vhd", 
            "storage_container": "jon-vm", 
            "storage_container_name": "jon-vm", 
            "subnet_name": null, 
            "subscription_id": null, 
            "tags": null, 
            "tenant": null, 
            "virtual_network_name": null, 
            "vm_size": "Standard_D1"
        }
    }, 
    "msg": "Error creating or updating virtual machine jon-vm - Azure Error: NetworkInterfaceMustHavePrimaryPropertySet\nMessage: Network interface jon-nic1 must have Primary property set."
}
```

<!--- Paste verbatim command output between quotes below -->
```
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "Error creating or updating virtual machine jon-vm - Azure Error: NetworkInterfaceMustHavePrimaryPropertySet\nMessage: Network interface jon-nic1 must have Primary property set."}
```

azure_rm_virtualmachine not create VM with multi NICs
<!---
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: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
azure_rm_virtualmachine

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
2.3.1.0
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
Nope

##### 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.
-->
Linux Ubuntu 16.10 Yaketty
Linux Debian 8 Jessy

##### SUMMARY
<!--- Explain the problem briefly -->
Is not possible to create a VM with more than one NIC.
The ```azure_rm_virtualmachine``` module return error.

##### 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.
-->

1. Getting Started with Azure
Follow this page https://docs.ansible.com/ansible/guide_azure.html

2. Run this playbook 
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create Azure VM
  hosts: localhost
  connection: local
  tasks:
    - name: Create storage account
      azure_rm_storageaccount:
        resource_group: RGtest2
        name: 'jonstorage01'
        account_type: Standard_LRS
    
    - name: Create virtual network
      azure_rm_virtualnetwork:
        resource_group: RGtest2
        name: 'jon-vn'
        address_prefixes: "10.1.0.0/16"
    
    - name: Add subnet
      azure_rm_subnet:
        resource_group: RGtest2
        name: 'jon-subnet1'
        address_prefix: "10.1.0.0/24"
        virtual_network: 'jon-vn'

    - name: Add subnet2
      azure_rm_subnet:
        resource_group: RGtest2
        name: 'jon-subnet2'
        address_prefix: "10.1.1.0/24"
        virtual_network: 'jon-vn'

    - name: Create public ip
      azure_rm_publicipaddress:
        resource_group: RGtest2
        allocation_method: Static
        name: 'jon-public-ip'
    
    - name: Create security group that allows SSH
      azure_rm_securitygroup:
        resource_group: RGtest2
        name: 'jon-securitygroup-ssh'
        rules:
          - name: SSH
            protocol: Tcp
            destination_port_range: 22
            access: Allow
            priority: 101
            direction: Inbound

    - name: Create security group all denied
      azure_rm_securitygroup:
        resource_group: RGtest2
        name: 'jon-securitygroup-alldenied'

    - name: Create NIC
      azure_rm_networkinterface:
        resource_group: RGtest2
        name: 'jon-nic1'
        virtual_network: 'jon-vn'
        subnet: 'jon-subnet1'
        public_ip_name: 'jon-public-ip'
        security_group: 'jon-securitygroup-ssh'

    - name: Create NIC02
      azure_rm_networkinterface:
        resource_group: RGtest2
        name: 'jon-nic2'
        virtual_network: 'jon-vn'
        subnet: 'jon-subnet2'
        public_ip: no
        security_group: 'jon-securitygroup-alldenied'

    - name: Create virtual machine
      azure_rm_virtualmachine:
        resource_group: RGtest2
        name: 'jon-vm'
        vm_size: Standard_D1
        storage_account: 'jonstorage01'
        storage_container: 'jon-vm'
        storage_blob: 'jon-vm01.vhd'
        admin_username: Ansible
        admin_password: Password99!
        network_interfaces: [ 'jon-nic1', 'jon-nic2' ]
        image:
          offer: Debian
          publisher: credativ
          sku: '8'
          version: latest
```

<!--- 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 VM.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Error, the VM is not created.
Problem with Primary property for network interfaces.

```
TASK [Create virtual machine] ********************************************************************************************************************************
task path: /home/jon/test/azure/azure.yml:88
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: jon
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
fatal: [localhost]: FAILED! => {
    "changed": false, 
    "failed": true, 
    "invocation": {
        "module_args": {
            "ad_user": null, 
            "admin_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
            "admin_username": "Ansible", 
            "allocated": true, 
            "append_tags": true, 
            "client_id": null, 
            "image": {
                "offer": "Debian", 
                "publisher": "credativ", 
                "sku": "8", 
                "version": "8.0.201706210"
            }, 
            "location": null, 
            "name": "jon-vm", 
            "network_interface_names": [
                "jon-nic1", 
                "jon-nic2"
            ], 
            "network_interfaces": [
                "jon-nic1", 
                "jon-nic2"
            ], 
            "open_ports": null, 
            "os_disk_caching": "ReadOnly", 
            "os_type": "Linux", 
            "password": null, 
            "profile": null, 
            "public_ip_allocation_method": "Static", 
            "remove_on_absent": [
                "all"
            ], 
            "resource_group": "RGtest2", 
            "restarted": false, 
            "secret": null, 
            "short_hostname": null, 
            "ssh_password_enabled": true, 
            "ssh_public_keys": null, 
            "started": true, 
            "state": "present", 
            "storage_account": "jonstorage01", 
            "storage_account_name": "jonstorage01", 
            "storage_blob": "jon-vm01.vhd", 
            "storage_blob_name": "jon-vm01.vhd", 
            "storage_container": "jon-vm", 
            "storage_container_name": "jon-vm", 
            "subnet_name": null, 
            "subscription_id": null, 
            "tags": null, 
            "tenant": null, 
            "virtual_network_name": null, 
            "vm_size": "Standard_D1"
        }
    }, 
    "msg": "Error creating or updating virtual machine jon-vm - Azure Error: NetworkInterfaceMustHavePrimaryPropertySet\nMessage: Network interface jon-nic1 must have Primary property set."
}
```

<!--- Paste verbatim command output between quotes below -->
```
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "msg": "Error creating or updating virtual machine jon-vm - Azure Error: NetworkInterfaceMustHavePrimaryPropertySet\nMessage: Network interface jon-nic1 must have Primary property set."}
```



Discussion:
cc @haroldwongms @nitzmahone @tstringer
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
We can use pull request #26887 to discuss.
cc @chouseknecht
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @xscript @yaweiw @zikalino
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @yuwzho
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @trstringer
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Since #32882 merged and covered multiple NIC, can we close this one? @yuwzho 
@kyliel no, #32882 is NIC with multiple ip configuration, this is about vm with multiple NIC.
cc @haroldwongms @nitzmahone @tstringer
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
We can use pull request #26887 to discuss.
cc @chouseknecht
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @xscript @yaweiw @zikalino
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @yuwzho
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @trstringer
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Since #32882 merged and covered multiple NIC, can we close this one? @yuwzho 
@kyliel no, #32882 is NIC with multiple ip configuration, this is about vm with multiple NIC.

Resolving patch:
diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
--- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
+++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachine.py
@@ -819,7 +819,8 @@ def exec_module(self, **kwargs):
                     if set(current_nics) != set(network_interfaces):
                         self.log('CHANGED: virtual machine {0} - network interfaces are different.'.format(self.name))
                         differences.append('Network Interfaces')
-                        updated_nics = [dict(id=id) for id in network_interfaces]
+                        updated_nics = [dict(id=id, primary=(i is 0))
+                                        for i, id in enumerate(network_interfaces)]
                         vm_dict['properties']['networkProfile']['networkInterfaces'] = updated_nics
                         changed = True
 
@@ -928,7 +929,8 @@ def exec_module(self, **kwargs):
                     if not self.short_hostname:
                         self.short_hostname = self.name
 
-                    nics = [self.compute_models.NetworkInterfaceReference(id=id) for id in network_interfaces]
+                    nics = [self.compute_models.NetworkInterfaceReference(id=id, primary=(i is 0))
+                            for i, id in enumerate(network_interfaces)]
 
                     # os disk
                     if self.managed_disk_type:
@@ -1057,9 +1059,8 @@ def exec_module(self, **kwargs):
 
                     self.log("Update virtual machine {0}".format(self.name))
                     self.results['actions'].append('Updated VM {0}'.format(self.name))
-
-                    nics = [self.compute_models.NetworkInterfaceReference(id=interface['id'])
-                            for interface in vm_dict['properties']['networkProfile']['networkInterfaces']]
+                    nics = [self.compute_models.NetworkInterfaceReference(id=interface['id'], primary=(i is 0))
+                            for i, interface in enumerate(vm_dict['properties']['networkProfile']['networkInterfaces'])]
 
                     # os disk
                     if not vm_dict['properties']['storageProfile']['osDisk'].get('managedDisk'):


Test patch:
diff --git a/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml b/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml
--- a/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml
+++ b/test/integration/targets/azure_rm_virtualmachine/tasks/virtualmachine.yml
@@ -1,9 +1,14 @@
-- name: Delete virtual machine
+- name: Delete virtual machines
   azure_rm_virtualmachine:
       resource_group: "{{ resource_group }}"
-      name: testvm002
+      name: "{{ vms }}"
       state: absent
       vm_size: Standard_A0
+  loop:
+    - testvm002
+    - testvm003
+  loop_control:
+    loop_var: vms
   register: output
 
 - name: Create storage account name
@@ -59,7 +64,7 @@
           priority: 110
           direction: Inbound
 
-- name: Create NIC
+- name: Create NIC for single nic VM
   azure_rm_networkinterface:
       resource_group: "{{ resource_group }}"
       name: testvm001
@@ -68,7 +73,7 @@
       public_ip_name: testvm001
       security_group: testvm001
 
-- name: Create virtual machine
+- name: Create virtual machine with a single NIC
   register: output
   azure_rm_virtualmachine:
       resource_group: "{{ resource_group }}"
@@ -173,7 +178,7 @@
           - "azure_vm.powerstate in ['starting', 'running']"
           - output.changed
 
-- name: Should be idempotent
+- name: Should be idempotent with a single NIC
   azure_rm_virtualmachine:
       resource_group: "{{ resource_group }}"
       name: testvm002
@@ -251,6 +256,82 @@
       state: absent
       vm_size: Standard_A0
 
+- name: Create NICs for dual nic VM
+  azure_rm_networkinterface:
+      resource_group: "{{ resource_group }}"
+      name: "{{ item }}"
+      virtual_network: testvm001
+      subnet: testvm001
+      security_group: testvm001
+  loop:
+    - testvm011
+    - testvm012
+
+- name: Create virtual machine with two NICs
+  register: output
+  vars:
+    niclist:
+      - testvm011
+      - testvm012
+  azure_rm_virtualmachine:
+      resource_group: "{{ resource_group }}"
+      name: testvm003
+      vm_size: Standard_A0
+      storage_account: "{{ storage_account }}"
+      storage_container: testvm001
+      storage_blob: testvm003.vhd
+      admin_username: adminuser
+      admin_password: Password123!
+      short_hostname: testvm
+      os_type: Linux
+      network_interfaces: "{{ niclist }}"
+      availability_set: "avbs{{ resource_group | hash('md5') | truncate(7, True, '') }}"
+      image:
+        offer: UbuntuServer
+        publisher: Canonical
+        sku: 16.04-LTS
+        version: latest
+
+- assert:
+      that:
+        - azure_vm.properties.availabilitySet.id
+
+- name: Should be idempotent with a dual NICs
+  vars:
+    niclist:
+      - testvm011
+      - testvm012
+  azure_rm_virtualmachine:
+      resource_group: "{{ resource_group }}"
+      name: testvm003
+      vm_size: Standard_A0
+      storage_account: "{{ storage_account }}"
+      storage_container: testvm001
+      storage_blob: testvm003.vhd
+      admin_username: adminuser
+      admin_password: Password123!
+      short_hostname: testvm
+      os_type: Linux
+      network_interfaces: "{{ niclist }}"
+      availability_set: "avbs{{ resource_group | hash('md5') | truncate(7, True, '') }}"
+      image:
+        offer: UbuntuServer
+        publisher: Canonical
+        sku: 16.04-LTS
+        version: latest
+  register: output
+
+- assert:
+      that: not output.changed
+
+- name: Delete dual NIC VM
+  azure_rm_virtualmachine:
+      resource_group: "{{ resource_group }}"
+      name: testvm003
+      state: absent
+      vm_size: Standard_A0
+  register: output
+
 # TODO: Until we have a module to create/delete images this is the best tests
 # I can do
 - name: assert error thrown with invalid image dict