Repository: ansible/ansible

Issue:
delegate_to with networking modules fails when using API
For networking modules delegate_to should work when using an API (for example, Arista eAPI or NX-OS NX-API)

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report


##### COMPONENT NAME
Network Action Plugin

##### ANSIBLE VERSION
```
$ ansible --version
ansible 2.4.0 (devel af9396841e) last updated 2017/08/28 11:30:16 (GMT -700)
  config file = /home/kbyers/.ansible.cfg
  configured module search path = [u'/home/kbyers/napalm-ansible/library', u'/home/kbyers/ansible-extras/ntc-ansible/library']
  ansible python module location = /home/kbyers/ansible/lib/ansible
  executable location = /home/kbyers/VENV/ansible_dev/bin/ansible
  python version = 2.7.12 (default, Sep  1 2016, 22:14:00) [GCC 4.8.3 20140911 (Red Hat 4.8.3-9)]

```

Also tested on 2.3.2.0

##### CONFIGURATION


##### OS / ENVIRONMENT
N/A

##### SUMMARY
Playbook configured to use delegate_to to a second server and setup to use nxos_facts module using a provider that uses NX-API will generate the following error:

```
fatal: [nxos1 -> anslab1a.twb-tech.com]: FAILED! => {"changed": false, "failed": true, "msg": "invalid connection specified, expected connection=local, got ssh"}
```

Commenting out the following six lines in `./lib/ansible/plugins/action/nxos.py` fixes the issue and the playbook works normally with delegate_to:

```
 40     def run(self, tmp=None, task_vars=None):
 41 #        if self._play_context.connection != 'local':
 42 #            return dict(
 43 #                failed=True,
 44 #                msg='invalid connection specified, expected connection=local, '
 45 #                    'got %s' % self._play_context.connection
 46 #            )
 ```

```
$ ansible-playbook test_delegate2.yml -i ./ansible-hosts 

PLAY [Test NX-OS API] *******************************************************************************************************************************

TASK [Verify connectivity] **************************************************************************************************************************
ok: [nxos1 -> anslab1a.twb-tech.com]
ok: [nxos2 -> anslab1a.twb-tech.com]

PLAY RECAP ******************************************************************************************************************************************
nxos1                      : ok=1    changed=0    unreachable=0    failed=0   
nxos2                      : ok=1    changed=0    unreachable=0    failed=0   

```


##### STEPS TO REPRODUCE


<!--- Paste example playbooks or commands between quotes below -->
```yaml
---
- name: Test NX-OS API
  hosts: nxos
  tasks:
    - name: Verify connectivity
      nxos_facts:
        provider: "{{ creds_core_nxapi }}"
      delegate_to: server2

```

Inventory file:
```
[local]
localhost ansible_connection=local

[server]
server2 ansible_host=anslab1a.twb-tech.com ansible_user=kbyers2 ansible_connection=smart

[server:vars]
ansible_python_interpreter=/home/kbyers2/VENV/ansible/bin/python

[nxos]
nxos1 ansible_host=nxos1.domain.com
nxos2 ansible_host=nxos2.domain.com

[nxos:vars]
username=pyclass
password=password
```

Provider
```yaml
creds_core_nxapi:
  host: "{{ ansible_host }}"
  username: "{{ username }}"
  password: "{{ password }}"
  transport: nxapi
  use_ssl: yes
  validate_certs: no
  port: 8443
  timeout: 60
```

##### EXPECTED RESULTS
delegate_to should work properly when using an API connection. In other words, delegate_to is expected to fail for Ansible core networking modules when using SSH (given the persistent SSH connections), but should continue to work for API connections.

##### ACTUAL RESULTS
<!--- Paste verbatim command output between quotes below -->
```
$ ansible-playbook test_delegate2.yml -i ./ansible-hosts 

PLAY [Test NX-OS API] *******************************************************************************************************************************

TASK [Verify connectivity] **************************************************************************************************************************
fatal: [nxos1 -> anslab1a.twb-tech.com]: FAILED! => {"changed": false, "failed": true, "msg": "invalid connection specified, expected connection=local, got ssh"}
fatal: [nxos2 -> anslab1a.twb-tech.com]: FAILED! => {"changed": false, "failed": true, "msg": "invalid connection specified, expected connection=local, got ssh"}

PLAY RECAP ******************************************************************************************************************************************
nxos1                      : ok=0    changed=0    unreachable=0    failed=1   
nxos2                      : ok=0    changed=0    unreachable=0    failed=1   
```

As noted above, playbook operates fine if I comment out the connection local check on lines 41 through 46 of the nxos.py action plugin


Discussion:
cc @Qalthos @ganeshrn @gundalow @privateip @rcarrillocruz @trishnaguha

Resolving patch:
diff --git a/lib/ansible/plugins/action/eos.py b/lib/ansible/plugins/action/eos.py
--- a/lib/ansible/plugins/action/eos.py
+++ b/lib/ansible/plugins/action/eos.py
@@ -37,16 +37,16 @@
 class ActionModule(_ActionModule):
 
     def run(self, tmp=None, task_vars=None):
-        if self._play_context.connection != 'local':
+        provider = load_provider(eos_provider_spec, self._task.args)
+        transport = provider['transport'] or 'cli'
+
+        if self._play_context.connection != 'local' and transport == 'cli':
             return dict(
                 failed=True,
                 msg='invalid connection specified, expected connection=local, '
                     'got %s' % self._play_context.connection
             )
 
-        provider = load_provider(eos_provider_spec, self._task.args)
-        transport = provider['transport'] or 'cli'
-
         display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
 
         if transport == 'cli':
diff --git a/lib/ansible/plugins/action/nxos.py b/lib/ansible/plugins/action/nxos.py
--- a/lib/ansible/plugins/action/nxos.py
+++ b/lib/ansible/plugins/action/nxos.py
@@ -37,16 +37,16 @@
 class ActionModule(_ActionModule):
 
     def run(self, tmp=None, task_vars=None):
-        if self._play_context.connection != 'local':
+        provider = load_provider(nxos_provider_spec, self._task.args)
+        transport = provider['transport'] or 'cli'
+
+        if self._play_context.connection != 'local' and transport == 'cli':
             return dict(
                 failed=True,
                 msg='invalid connection specified, expected connection=local, '
                     'got %s' % self._play_context.connection
             )
 
-        provider = load_provider(nxos_provider_spec, self._task.args)
-        transport = provider['transport'] or 'cli'
-
         display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
 
         if transport == 'cli':


Test patch: