Repository: ansible/ansible

Issue:
Delegation of service action fails when using different init systems
Hello ansible devs,
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

`service` action, as determined by `lib/ansible/plugins/action/service.py`
##### ANSIBLE VERSION

```
ansible 2.2.0 (devel 1c33b5a9f0) last updated 2016/08/30 09:23:53 (GMT +200)
  lib/ansible/modules/core: (detached HEAD 5310bab12f) last updated 2016/08/30 09:23:59 (GMT +200)
  lib/ansible/modules/extras: (detached HEAD 2ef4a34eee) last updated 2016/08/30 09:23:59 (GMT +200)
  config file = 
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

nothing specific to this bug
##### OS / ENVIRONMENT

Debian stretch/testing on amd64, clients are wheezy systems running sysvinit and jessie systems running systemd.
##### SUMMARY

Depending on the gathered host systems facts, ansible decides which `service_mgr` to use. On systemd systems, this is going to be `systemd`, on sysvinit systems, it's `service` and so on. The problem is, when delegating a service action from a system running systemd to a system running something else, ansible still uses the systemd service_mgr, which obviously fails.

A workaround is forcing the action to `service` with the un(der)documented `use` option as shown in the third play.
##### STEPS TO REPRODUCE

consider the following playbook. on some_sysvinit_host, the icinga service needs to be restarted. This fails when that action is delegated and no special care is taken to work around the bug:

```
- hosts: monitoring_running_sysvinit
  name: Verify that direct service restart works
  tasks:
    - service: name=icinga state=restarted

- hosts: client_with_systemd
  name: Check whether it falls back to generic action w/o facts
  gather_facts: false
  tasks:
    - service: name=icinga state=restarted
      delegate_to: monitoring_running_sysvinit

- hosts: client_with_systemd
  name: Delegated to host running sysvinit, force 'service' handler
  tasks:
    - service: name=icinga state=restarted use=service
      delegate_to: monitoring_running_sysvinit

- hosts: client_with_systemd
  name: Delegated to host running sysvinit
  tasks:
    - service: name=icinga state=restarted
      delegate_to: monitoring_running_sysvinit
```
##### EXPECTED RESULTS

All four actions should restart the icinga service on monitoring_running_sysvinit successfully.
##### ACTUAL RESULTS

The first action, running directly on the monitoring_running_sysvinit host restarts correctly.

Delegation works when we're working around the bug by either not gathering facts (and so, forcing client_with_systemd's service_mgr to the fallback `service`) or by overwriting the helper action to `service` by setting the `use` action for the delegated service action.

When just delegating from the systemd host to the sysvinit (or any other combination), the incompatible action fails:

```
$ ansible-playbook test-delegation-service.yml  --sudo --verbose --diff
PLAY [Verify that direct service restart works] ********************************

TASK [setup] *******************************************************************
ok: [monitoring_running_sysvinit]

TASK [service] *****************************************************************
changed: [monitoring_running_sysvinit] => {"changed": true, "name": "icinga", "state": "started"}

PLAY [Check whether it falls back to generic action w/o facts] *****************

TASK [service] *****************************************************************
changed: [client_with_systemd -> monitoring_running_sysvinit] => {"changed": true, "name": "icinga", "state": "started"}

PLAY [Delegated to host running sysvinit, force 'service' handler] *************

TASK [setup] *******************************************************************
ok: [client_with_systemd]

TASK [service] *****************************************************************
changed: [client_with_systemd -> monitoring_running_sysvinit] => {"changed": true, "name": "icinga", "state": "started"}

PLAY [Delegated to host running sysvinit] **************************************

TASK [setup] *******************************************************************
ok: [client_with_systemd]

TASK [service] *****************************************************************
fatal: [client_with_systemd -> monitoring_running_sysvinit]: FAILED! => {"changed": false, "cmd": "None show icinga", "failed": true, "msg": "[Errno 2] No such file or directory", "rc": 2}
        to retry, use: --limit @test-delegation-service.retry

PLAY RECAP *********************************************************************
client_with_systemd               : ok=4    changed=2    unreachable=0    failed=1   
monitoring_running_sysvinit       : ok=2    changed=1    unreachable=0    failed=0   

```



Discussion:
as a workaround you can set it with the `use=` option to the correct managment plugin

@bcoca, i know, see the third play in my example playbook :)

not gathering facts works too. as that disables the autodetection (second play)


Resolving patch:
diff --git a/lib/ansible/plugins/action/package.py b/lib/ansible/plugins/action/package.py
--- a/lib/ansible/plugins/action/package.py
+++ b/lib/ansible/plugins/action/package.py
@@ -41,7 +41,10 @@ def run(self, tmp=None, task_vars=None):
 
         if module == 'auto':
             try:
-                module = self._templar.template('{{ansible_pkg_mgr}}')
+                if self._task.delegate_to: # if we delegate, we should use delegated host's facts
+                    module = self._templar.template("{{hostvars['%s']['ansible_pkg_mgr']}}" % self._task.delegate_to)
+                else:
+                    module = self._templar.template('{{ansible_pkg_mgr}}')
             except:
                 pass # could not get it from template!
 
diff --git a/lib/ansible/plugins/action/service.py b/lib/ansible/plugins/action/service.py
--- a/lib/ansible/plugins/action/service.py
+++ b/lib/ansible/plugins/action/service.py
@@ -40,7 +40,10 @@ def run(self, tmp=None, task_vars=None):
 
         if module == 'auto':
             try:
-                module = self._templar.template('{{ansible_service_mgr}}')
+                if self._task.delegate_to: # if we delegate, we should use delegated host's facts
+                    module = self._templar.template("{{hostvars['%s']['ansible_service_mgr']}}" % self._task.delegate_to)
+                else:
+                    module = self._templar.template('{{ansible_service_mgr}}')
             except:
                 pass # could not get it from template!
 


Test patch: