summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Krizek <martin.krizek@gmail.com>2021-06-03 09:26:22 +0200
committerGitHub <noreply@github.com>2021-06-03 09:26:22 +0200
commite201b542be23bccd2418eab661cdf5454af3bea8 (patch)
tree7d5d12df8c39d87ef0b84fd753993cf90c063cc8
parentfirst_found: clear up the skip option usage (#74816) (diff)
downloadansible-e201b542be23bccd2418eab661cdf5454af3bea8.tar.xz
ansible-e201b542be23bccd2418eab661cdf5454af3bea8.zip
Ensure end_play ends play, not batch (#74332)
* Ensure end_play ends play, not batch Fixes #73971 ci_complete * Preserve result * Move AnsibleEndPlay to TQM * Add tests * Add changelog * Explaining comment * Fix changelog name * ci_complete
-rw-r--r--changelogs/fragments/73971-non-batch-end_play.yml2
-rw-r--r--lib/ansible/executor/play_iterator.py2
-rw-r--r--lib/ansible/executor/playbook_executor.py9
-rw-r--r--lib/ansible/executor/task_queue_manager.py8
-rw-r--r--lib/ansible/plugins/strategy/__init__.py3
-rwxr-xr-xtest/integration/targets/meta_tasks/runme.sh6
-rw-r--r--test/integration/targets/meta_tasks/test_end_play_serial_one.yml13
7 files changed, 41 insertions, 2 deletions
diff --git a/changelogs/fragments/73971-non-batch-end_play.yml b/changelogs/fragments/73971-non-batch-end_play.yml
new file mode 100644
index 0000000000..6445d9ff37
--- /dev/null
+++ b/changelogs/fragments/73971-non-batch-end_play.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - Ensure end_play ends play, not batch (https://github.com/ansible/ansible/issues/73971)
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
index 10494bc341..96f87c2cc9 100644
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -216,6 +216,8 @@ class PlayIterator:
# plays won't try to advance)
play_context.start_at_task = None
+ self.end_play = False
+
def get_host_state(self, host):
# Since we're using the PlayIterator to carry forward failed hosts,
# in the event that a previous host was not in the current inventory
diff --git a/lib/ansible/executor/playbook_executor.py b/lib/ansible/executor/playbook_executor.py
index bfaecba91b..e0d3a645aa 100644
--- a/lib/ansible/executor/playbook_executor.py
+++ b/lib/ansible/executor/playbook_executor.py
@@ -23,7 +23,7 @@ import os
from ansible import constants as C
from ansible import context
-from ansible.executor.task_queue_manager import TaskQueueManager
+from ansible.executor.task_queue_manager import TaskQueueManager, AnsibleEndPlay
from ansible.module_utils._text import to_text
from ansible.module_utils.parsing.convert_bool import boolean
from ansible.plugins.loader import become_loader, connection_loader, shell_loader
@@ -186,7 +186,12 @@ class PlaybookExecutor:
# restrict the inventory to the hosts in the serialized batch
self._inventory.restrict_to_hosts(batch)
# and run it...
- result = self._tqm.run(play=play)
+ try:
+ result = self._tqm.run(play=play)
+ except AnsibleEndPlay as e:
+ result = e.result
+ break_play = True
+ break
# break the play if the result equals the special return code
if result & self._tqm.RUN_FAILED_BREAK_PLAY != 0:
diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py
index eaa262d87a..48ebf1aaff 100644
--- a/lib/ansible/executor/task_queue_manager.py
+++ b/lib/ansible/executor/task_queue_manager.py
@@ -81,6 +81,11 @@ class FinalQueue(multiprocessing.queues.Queue):
)
+class AnsibleEndPlay(Exception):
+ def __init__(self, result):
+ self.result = result
+
+
class TaskQueueManager:
'''
@@ -323,6 +328,9 @@ class TaskQueueManager:
for host_name in iterator.get_failed_hosts():
self._failed_hosts[host_name] = True
+ if iterator.end_play:
+ raise AnsibleEndPlay(play_return)
+
return play_return
def cleanup(self):
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
index 90f7cbc19d..7f4dafa4c9 100644
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -1161,6 +1161,9 @@ class StrategyBase:
for host in self._inventory.get_hosts(iterator._play.hosts):
if host.name not in self._tqm._unreachable_hosts:
iterator._host_states[host.name].run_state = iterator.ITERATING_COMPLETE
+ # end_play is used in PlaybookExecutor/TQM to indicate that
+ # the whole play is supposed to be ended as opposed to just a batch
+ iterator.end_play = True
msg = "ending play"
else:
skipped = True
diff --git a/test/integration/targets/meta_tasks/runme.sh b/test/integration/targets/meta_tasks/runme.sh
index 3ee419cb06..cc951bbba5 100755
--- a/test/integration/targets/meta_tasks/runme.sh
+++ b/test/integration/targets/meta_tasks/runme.sh
@@ -49,4 +49,10 @@ for test_strategy in linear free; do
grep -q "META: ending play" <<< "$out"
grep -qv 'Failed to end using end_play' <<< "$out"
+
+ out="$(ansible-playbook test_end_play_serial_one.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")"
+
+ [ "$(grep -c "Testing end_play on host" <<< "$out" )" -eq 1 ]
+ grep -q "META: ending play" <<< "$out"
+ grep -qv 'Failed to end using end_play' <<< "$out"
done
diff --git a/test/integration/targets/meta_tasks/test_end_play_serial_one.yml b/test/integration/targets/meta_tasks/test_end_play_serial_one.yml
new file mode 100644
index 0000000000..f838d4a6bc
--- /dev/null
+++ b/test/integration/targets/meta_tasks/test_end_play_serial_one.yml
@@ -0,0 +1,13 @@
+- name: Testing end_play with serial 1 and strategy {{ test_strategy | default('linear') }}
+ hosts: testhost:testhost2
+ gather_facts: no
+ serial: 1
+ strategy: "{{ test_strategy | default('linear') }}"
+ tasks:
+ - debug:
+ msg: "Testing end_play on host {{ inventory_hostname }}"
+
+ - meta: end_play
+
+ - fail:
+ msg: 'Failed to end using end_play'