diff --git a/tripleo_ansible/ansible_plugins/strategy/linear.py b/tripleo_ansible/ansible_plugins/strategy/linear.py new file mode 100644 index 000000000..f0ef84956 --- /dev/null +++ b/tripleo_ansible/ansible_plugins/strategy/linear.py @@ -0,0 +1,203 @@ +# Copyright 2020 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import imp +import os + +import ansible.plugins.strategy as strategy +LINEAR = imp.load_source( + 'linear', + os.path.join(os.path.dirname(strategy.__file__), 'linear.py') +) + + +class StrategyModule(LINEAR.StrategyModule): + """Notes about this strategy optimization. + + To improve execution speed, if a task has a conditional attached to + it, it will be evaluated server side, before queuing. + """ + + def __init__(self, *args, **kwargs): + self.hostvars = {} + self.host_role_cache = {} + super(StrategyModule, self).__init__(*args, **kwargs) + + def _check_when(self, host, task, task_vars): + """Evaluate if a task is to be executed. + + :param host: object + :param task: object + :param task_vars: dict + :retruns: boolean + """ + try: + conditional = task.evaluate_conditional( + LINEAR.Templar( + loader=self._loader, + variables=task_vars + ), + task_vars + ) + if not conditional: + LINEAR.display.verbose( + u'Task "{}" has been omitted from the job because the' + u' conditional "{}" was evaluated as "{}"'.format( + task.name or None, + task.when, + conditional + ), + host=host, + caplevel=3 + ) + return False + except Exception: + return True + else: + return True + + def _get_next_task_lockstep(self, hosts, iterator): + host_tasks = super(StrategyModule, self)._get_next_task_lockstep( + hosts, iterator) + + # If no tasks were returned at all, just return + if not host_tasks: + return host_tasks + + new_host_tasks = [] + role_when_cache = {} + + LINEAR.display.vv("\n") + + for h, t in host_tasks: + LINEAR.display.vv( + "skip_once_per_role: " + "Checking host {} for task {}".format( + h.name, t and t.name or "None")) + task_vars = {} + # task is None, assume all others are as well return the original list + if t is None: + LINEAR.display.vv( + " skip_once_per_role: " + "task is None, returning host_tasks") + return host_tasks + # task is meta, always append it to the new list + elif t.action == 'meta': + # Use vvv here as this gets logged a lot + LINEAR.display.vvv( + " skip_once_per_role: " + "task is meta, appending") + # We can't just return host_tasks here, as the task list could + # be a mix of meta (noop) tasks and real tasks, depending on + # what hosts the task is set to run for. We need to continue + # checking the rest of the tasks. + new_host_tasks.append((h, t)) + continue + # task has no when argument, append it to the new list + elif not t.when: + LINEAR.display.vv( + " skip_once_per_role: " + "task has no when, appending") + new_host_tasks.append((h, t)) + continue + # task has a when argument, but also a register argument, append it + # to the new list + elif t.when and t.register: + LINEAR.display.vv( + " skip_once_per_role: " + "task has when and register, appending") + new_host_tasks.append((h, t)) + continue + + # Check if this host's role is already in the cache + role = self.host_role_cache.get(h.name, '') + + # Check if the host belongs to an inventory group that has the + # same name as one of the roles we have already seen. + # If so, assume that is the host's role, and add it to the + # cache. + if not role: + group_names = [g.name for g in h.groups] + for r in set(self.host_role_cache.values()): + if r in group_names: + role = r + self.host_role_cache[h.name] = role + break + + # Still no role was found, attempt to look it up using + # hostvars. + if not role: + if not self.hostvars: + if not task_vars: + task_vars = self._variable_manager.get_vars(play=iterator._play, host=h, task=t) + self.hostvars = task_vars['hostvars'] + role = self.hostvars[h.name].get('tripleo_role_name', '') + self.host_role_cache[h.name] = role + + LINEAR.display.vv( + " skip_once_per_role: " + "host {} has role {}".format(h.name, role)) + # role was found, it's in role_when_cache, and the value is True, + # append it to the new list. + if role and role in role_when_cache: + if role_when_cache[role]: + LINEAR.display.vv( + " skip_once_per_role: " + "task when is True from cache, " + "appending") + new_host_tasks.append((h, t)) + else: + LINEAR.display.vv( + " skip_once_per_role: " + "task when is False from cache, " + "skipping") + # role is not in the role_when_cache, check the when statement, add + # the result to the cache, and if True, append the task to the new list + elif role: + if not task_vars: + task_vars = self._variable_manager.get_vars(play=iterator._play, host=h, task=t) + if self._check_when(h, t, task_vars): + LINEAR.display.vv( + " skip_once_per_role: " + "task when evaluated to True, " + "appending") + new_host_tasks.append((h, t)) + role_when_cache[role] = True + else: + LINEAR.display.vv( + " skip_once_per_role: " + "task when evaluated to False, " + "skipping") + role_when_cache[role] = False + # role was never found, just append the task to the new list + else: + LINEAR.display.vv( + " skip_once_per_role: " + "host {} role not found, " + "appending".format(h.name)) + new_host_tasks.append((h, t)) + + # can't return an empty list of tasks, or ansible assumes the PLAY is + # done. As all tasks may have been removed, we need to add at least one + # back if the new list is empty. + if not new_host_tasks: + LINEAR.display.vv( + " skip_once_per_role: " + "empty host_tasks, " + "appending last seen task") + new_host_tasks.append((h, t)) + + return new_host_tasks