Repository: ansible/ansible

Issue:
meta: clear_host_errors not working on tasks
<!---
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 -->
`plugins/strategy/__init__.py`

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
Fresh install

##### 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.
-->
RHEL
##### SUMMARY
<!--- Explain the problem briefly -->

`meta: clear_host_errors`   is not working as expected

##### 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
- hosts: all
  gather_facts: no
  tasks:

  - name: dummy check 1
    changed_when: no
    failed_when: no
    ignore_errors: yes
    shell: uname

  - meta: clear_host_errors

  - name: dummy check 2
    changed_when: no
    failed_when: no
    ignore_errors: yes
    shell: uname -n


```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
$ ansible-playbook -i localhost,google.com  dummy.yml

PLAY [all] *********************************************************************

TASK [dummy check 1] ***********************************************************
ok: [localhost]
fatal: [google.com]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host google.com port 22: Connection timed out\r\n", "unreachable": true}

TASK [dummy check 2] ***********************************************************
ok: [localhost]
fatal: [google.com]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host google.com port 22: Connection timed out\r\n", "unreachable": true}

PLAY RECAP *********************************************************************
google.com                 : ok=0    changed=0    unreachable=2    failed=0
localhost                  : ok=2    changed=0    unreachable=0    failed=0


##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Expected to use unreachable host in the subsequent task. As per  https://github.com/ansible/ansible/pull/18127/files 
 "Connection failures set hosts as 'UNREACHABLE', which will remove them from the list of active hosts for the run. To recover from these issues you can use `meta: clear_host_errors` to have all currently flagged hosts reactivated, so subsequent tasks can try to use them again."

<!--- Paste verbatim command output between quotes below -->
```
$ ansible-playbook -i localhost,google.com  dummy.yml

PLAY [all] *********************************************************************

TASK [dummy check 1] ***********************************************************
ok: [localhost]
fatal: [google.com]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host google.com port 22: Connection timed out\r\n", "unreachable": true}

TASK [dummy check 2] ***********************************************************
ok: [localhost]
        to retry, use: --limit @/root/dummy.retry

PLAY RECAP *********************************************************************
google.com                 : ok=0    changed=0    unreachable=1    failed=0
localhost                  : ok=2    changed=0    unreachable=0    failed=0

```



Discussion:

Resolving patch:
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -854,7 +854,7 @@ def _take_step(self, task, host=None):
 
         return ret
 
-    def _execute_meta(self, task, play_context, iterator, target_host=None):
+    def _execute_meta(self, task, play_context, iterator, target_host):
 
         # meta tasks store their args in the _raw_params field of args,
         # since they do not use k=v pairs, so get that
@@ -865,52 +865,52 @@ def _execute_meta(self, task, play_context, iterator, target_host=None):
         #   on a meta task that doesn't support them
 
         def _evaluate_conditional(h):
-            all_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task)
+            all_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=h, task=task)
             templar = Templar(loader=self._loader, variables=all_vars)
             return task.evaluate_conditional(templar, all_vars)
 
-        if target_host:
-            host_list = [target_host]
-        else:
-            host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts]
-
-        results = []
-        for host in host_list:
-            result = None
-            if meta_action == 'noop':
-                # FIXME: issue a callback for the noop here?
-                result = TaskResult(host, task, dict(changed=False, msg="noop"))
-            elif meta_action == 'flush_handlers':
-                self.run_handlers(iterator, play_context)
-            elif meta_action == 'refresh_inventory':
-                self._inventory.refresh_inventory()
-                result = TaskResult(host, task, dict(changed=False, msg="inventory successfully refreshed"))
-            elif meta_action == 'clear_facts':
-                if _evaluate_conditional(host):
-                    self._variable_manager.clear_facts(target_host)
-                    result = TaskResult(host, task, dict(changed=True, msg="inventory successfully refreshed"))
-                else:
-                    result = TaskResult(host, task, dict(changed=False, skipped=True))
-            elif meta_action == 'clear_host_errors':
-                if _evaluate_conditional(host):
+        skipped = False
+        msg = ''
+        if meta_action == 'noop':
+            # FIXME: issue a callback for the noop here?
+            msg="noop"
+        elif meta_action == 'flush_handlers':
+            self.run_handlers(iterator, play_context)
+            msg = "ran handlers"
+        elif meta_action == 'refresh_inventory':
+            self._inventory.refresh_inventory()
+            msg = "inventory successfully refreshed"
+        elif meta_action == 'clear_facts':
+            if _evaluate_conditional(target_host):
+                for host in self._inventory.get_hosts(iterator._play.hosts):
+                    self._variable_manager.clear_facts(host)
+                msg = "facts cleared"
+            else:
+                skipped = True
+        elif meta_action == 'clear_host_errors':
+            if _evaluate_conditional(target_host):
+                for host in self._inventory.get_hosts(iterator._play.hosts):
                     self._tqm._failed_hosts.pop(host.name, False)
                     self._tqm._unreachable_hosts.pop(host.name, False)
                     iterator._host_states[host.name].fail_state = iterator.FAILED_NONE
-                    result = TaskResult(host, task, dict(changed=True, msg="successfully cleared host errors"))
-                else:
-                    result = TaskResult(host, task, dict(changed=False, skipped=True))
-            elif meta_action == 'end_play':
-                if _evaluate_conditional(host):
-                    iterator._host_states[host.name].run_state = iterator.ITERATING_COMPLETE
-                    result = TaskResult(host, task, dict(changed=True, msg="ending play"))
-                else:
-                    result = TaskResult(host, task, dict(changed=False, skipped=True))
-            #elif meta_action == 'reset_connection':
-            #    connection_info.connection.close()
+                msg="cleared host errors"
             else:
-                raise AnsibleError("invalid meta action requested: %s" % meta_action, obj=task._ds)
+                skipped = True
+        elif meta_action == 'end_play':
+            if _evaluate_conditional(target_host):
+                for host in self._inventory.get_hosts(iterator._play.hosts):
+                    if not host.name in  self._tqm._unreachable_hosts:
+                        iterator._host_states[host.name].run_state = iterator.ITERATING_COMPLETE
+                msg="ending play"
+        #elif meta_action == 'reset_connection':
+        #    connection_info.connection.close()
+        else:
+            raise AnsibleError("invalid meta action requested: %s" % meta_action, obj=task._ds)
 
-            if result is not None:
-                results.append(result)
+        result = { 'msg': msg }
+        if skipped:
+            result['skipped'] = True
+        else:
+            result['changed'] = False
 
-        return results
+        return [TaskResult(target_host, task, result)]
diff --git a/lib/ansible/plugins/strategy/linear.py b/lib/ansible/plugins/strategy/linear.py
--- a/lib/ansible/plugins/strategy/linear.py
+++ b/lib/ansible/plugins/strategy/linear.py
@@ -218,7 +218,7 @@ def run(self, iterator, play_context):
                     if task.action == 'meta':
                         # for the linear strategy, we run meta tasks just once and for
                         # all hosts currently being iterated over rather than one host
-                        results.extend(self._execute_meta(task, play_context, iterator))
+                        results.extend(self._execute_meta(task, play_context, iterator, host))
                         if task.args.get('_raw_params', None) != 'noop':
                             run_once = True
                     else:


Test patch: