diff --git a/playbooks/ansible-test-splitter/run.yaml b/playbooks/ansible-test-splitter/run.yaml index f69ec0253..50920fd22 100644 --- a/playbooks/ansible-test-splitter/run.yaml +++ b/playbooks/ansible-test-splitter/run.yaml @@ -4,8 +4,3 @@ - name: Run ansible-test-splitter include_role: name: ansible-test-splitter - vars: - ansible_test_test_command: "{{ ansible_test_command }}" - ansible_test_location: "~/{{ zuul.projects[ansible_collections_repo].src_dir }}" - ansible_test_git_branch: "{{ zuul.projects['github.com/ansible/ansible'].checkout }}" - ansible_test_ansible_path: "~/{{ zuul.projects['github.com/ansible/ansible'].src_dir }}" diff --git a/roles/ansible-test-splitter/defaults/main.yaml b/roles/ansible-test-splitter/defaults/main.yaml index cf90de94c..675709172 100644 --- a/roles/ansible-test-splitter/defaults/main.yaml +++ b/roles/ansible-test-splitter/defaults/main.yaml @@ -1,3 +1,6 @@ --- ansible_test_splitter__test_changed: false -ansible_test_splitter__children_prefix: please_adjust_this +# Git repositories of changed collections, only when +# ansible_test_splitter__test_changed is true +ansible_test_splitter__check_for_changes_in: + - "~/{{ zuul.projects[zuul.project.canonical_name].src_dir }}" diff --git a/roles/ansible-test-splitter/files/list_all.py b/roles/ansible-test-splitter/files/list_all.py deleted file mode 100644 index 4c90d0250..000000000 --- a/roles/ansible-test-splitter/files/list_all.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 - -from pathlib import PosixPath - -ignore = set(["unsupported", "disabled", "unstable", "hidden"]) - -targets_to_test = [] -targets_dir = PosixPath("tests/integration/targets") -for t in targets_dir.iterdir(): - aliases = t / "aliases" - if not aliases.is_file(): - continue - alias_content = aliases.read_text().split("\n") - if not ignore.isdisjoint(set(alias_content)): - continue - targets_to_test.append(t.stem) - -print(" ".join(targets_to_test)) diff --git a/roles/ansible-test-splitter/files/list_changed_targets.py b/roles/ansible-test-splitter/files/list_changed_targets.py index 8d6caf250..e30c8d0b7 100644 --- a/roles/ansible-test-splitter/files/list_changed_targets.py +++ b/roles/ansible-test-splitter/files/list_changed_targets.py @@ -1,45 +1,251 @@ #!/usr/bin/env python3 +import argparse +import ast +import json from pathlib import PosixPath import sys import subprocess +import yaml + + +parser = argparse.ArgumentParser( + description="Evaluate which targets need to be tested." +) +parser.add_argument( + "--branch", type=str, default="main", help="the default branch to test against" +) + +parser.add_argument( + "--test-all-the-targets", + dest="test_all_the_targets", + action="store_true", + default=False, + help="list all the target available in the the collection", +) + +parser.add_argument( + "--test-changed", + dest="test_changed", + action="store_true", + default=False, + help=("only test the targets impacted by the changes"), +) + + +parser.add_argument( + "collection_to_tests", + default=[], + nargs="+", + type=PosixPath, + help="the location of the collections to test. e.g: ~/.ansible/collections/ansible_collections/amazon/aws", +) targets_to_test = [] targets_dir = PosixPath("tests/integration/targets") -zuul_branch = sys.argv[1] -diff = subprocess.check_output( - ["git", "diff", f"origin/{zuul_branch}", "--name-only"] -).decode() -module_files = [PosixPath(d) for d in diff.split("\n") if d.startswith("plugins/")] -for i in module_files: - if not i.is_file(): - continue - target_name = i.stem - - for t in targets_dir.iterdir(): - aliases = t / "aliases" - if not aliases.is_file(): - continue - # There is a target with the module name, let's take that - if t.name == target_name: - targets_to_test.append(target_name) - break - alias_content = aliases.read_text().split("\n") - # The target name is in the aliases file - if target_name in alias_content: - targets_to_test.append(target_name) - break - -target_files = [ - PosixPath(d) for d in diff.split("\n") if d.startswith("tests/integration/targets/") -] -for i in target_files: - splitted = str(i).split("/") - if len(splitted) < 5: - continue - target_name = splitted[3] - aliases = targets_dir / target_name / "aliases" - if aliases.is_file(): - targets_to_test.append(target_name) - -print(" ".join(list(set(targets_to_test)))) + + +def parse_args(raw_args): + return parser.parse_args(raw_args) + + +def read_collection_name(path): + with (path / "galaxy.yml").open() as fd: + content = yaml.safe_load(fd) + return f'{content["namespace"]}.{content["name"]}' + + +def list_pyimport(collection_name, module_content): + root = ast.parse(module_content) + for node in ast.iter_child_nodes(root): + if isinstance(node, ast.Import): + yield node.names[0].name + elif isinstance(node, ast.ImportFrom): + module = node.module.split(".") + prefix = ( + f"ansible_collections.{collection_name}.plugins." + if node.level == 2 + else "" + ) + yield f"{prefix}{'.'.join(module)}" + + +class WhatHaveChanged: + def __init__(self, path, branch): + assert isinstance(path, PosixPath) + self.collection_path = path + self.branch = branch + self.collection_name = lambda: read_collection_name(path) + + def changed_files(self): + """List of changed files + + Returns a list of pathlib.PosixPath + """ + return [ + PosixPath(p) + for p in ( + subprocess.check_output( + [ + "git", + "diff", + f"origin/{self.branch}", + "--name-only", + ], + cwd=self.collection_path, + ) + .decode() + .split("\n") + ) + ] + + def modules(self): + """List the modules impacted by the change""" + for d in self.changed_files(): + if str(d).startswith("plugins/modules/"): + yield PosixPath(d) + + def module_utils(self): + """List the Python modules impacted by the change""" + for d in self.changed_files(): + if str(d).startswith("plugins/module_utils/"): + yield f"ansible_collections.{self.collection_name()}.plugins.module_utils.{d.stem}" + + +class Target: + def __init__(self, path): + self.path = path + self.lines = [l.split("#")[0] for l in path.read_text().split("\n") if l] + self.name = path.parent.name + + def is_alias_of(self, name): + return name in self.lines or self.name == name + + def is_unstable(self): + if "unstable" in self.lines: + return True + return False + + def is_disabled(self): + if "disabled" in self.lines: + return True + return False + + def is_slow(self): + if "slow" in self.lines or "# reason: slow" in self.lines: + return True + return False + + def is_ignored(self): + """Show the target be ignored by default?""" + ignore = set(["unsupported", "disabled", "unstable", "hidden"]) + return not ignore.isdisjoint(set(self.lines)) + + +class Collection: + def __init__(self, path): + self.collection_path = path + self._my_test_plan = [] + self.collection_name = lambda: read_collection_name(path) + + def _targets(self): + for a in self.collection_path.glob("tests/integration/targets/*/aliases"): + yield Target(a) + + def cover_all(self): + """Cover all the targets available.""" + for t in self._targets(): + if t.is_ignored(): + continue + self._my_test_plan.append(t) + + def cover_module(self, path): + """Track the targets to run follow up to a modules changed.""" + for t in self._targets(): + if t.is_disabled(): + continue + if t.is_alias_of(path.stem): + self._my_test_plan.append(t) + + def cover_module_utils(self, pymod): + """Track the targets to run follow up to a module_utils changed.""" + for m in self.collection_path.glob("plugins/modules/*"): + for i in list_pyimport(self.collection_name(), m.read_text()): + if pymod == i: + self.cover_module(m) + + def slow_targets_to_test(self): + return sorted(list(set([t.name for t in self._my_test_plan if t.is_slow()]))) + + def regular_targets_to_test(self): + return sorted( + list(set([t.name for t in self._my_test_plan if not t.is_slow()])) + ) + + +class ElGrandeSeparator: + def __init__(self, collections): + self.collections = collections + self.total_jobs = 13 # aka slot + self.targets_per_slot = 20 + + def output(self): + batches = [] + for c in collections: + slots = [ + f"integration-{c.collection_name()}-{i+1}" + for i in range(self.total_jobs) + ] + for b in self.build_up_batches(slots, c): + batches.append(b) + result = self.build_result_struct(batches) + print(json.dumps(result)) + + def build_up_batches(self, slots, c): + slow_targets = c.slow_targets_to_test() + regular_targets = c.regular_targets_to_test() + my_slot_available = [s for s in slots] + for i in slow_targets: + my_slot = my_slot_available.pop(0) + yield (my_slot, [i]) + + while regular_targets: + my_slot = my_slot_available.pop(0) + yield (my_slot, regular_targets[0 : self.targets_per_slot]) + for _ in range(self.targets_per_slot): + if regular_targets: + regular_targets.pop(0) + + def build_result_struct(self, batches): + result = { + "data": { + "zuul": {"child_jobs": []}, + "child": {"targets_to_test": {}}, + } + } + + for job, targets in batches: + result["data"]["zuul"]["child_jobs"].append(job) + result["data"]["child"]["targets_to_test"][job] = " ".join(targets) + return result + + +if __name__ == "__main__": + args = parse_args(sys.argv[1:]) + + collections = [Collection(i) for i in args.collection_to_tests] + + if args.test_all_the_targets: + for c in collections: + c.cover_all() + else: + for whc in [WhatHaveChanged(i, args.branch) for i in args.collection_to_tests]: + for path in whc.modules(): + for c in collections: + c.cover_module(path) + for path in whc.module_utils(): + for c in collections: + c.cover_module_utils(path) + + egs = ElGrandeSeparator(collections) + egs.output() diff --git a/roles/ansible-test-splitter/files/split_targets.py b/roles/ansible-test-splitter/files/split_targets.py deleted file mode 100644 index f260f53f6..000000000 --- a/roles/ansible-test-splitter/files/split_targets.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 - -from pathlib import PosixPath -import sys -import json -import argparse -import os - - -total_jobs = 6 - - -def get_job_list(prefix, total_jobs): - return [f"{prefix}{i}" for i in range(total_jobs)] - - -def to_skip_because_unstable(lines): - if "unstable" in lines: - return True - return False - - -def to_skip_because_disabled(lines): - if "disabled" in lines: - return True - return False - - -def is_slow(lines): - if "slow" in lines or "# reason: slow" in lines: - return True - return False - - -def get_all_targets(collection_path): - """Return all the targets from a directory.""" - targets_dir = PosixPath(os.path.join(collection_path, "tests/integration/targets/")) - targets = {} - for target in targets_dir.glob("*"): - aliases = target / "aliases" - if not target.is_dir(): - continue - if not aliases.is_file(): - continue - lines = aliases.read_text().split("\n") - targets[target.name] = lines - return targets - - -def to_skip_because_of_targets_parameters(target_name, lines, targets_from_cli): - """--targets parameter is in use, we skip the targets not part of the list.""" - combined = lines + [target_name] - if targets_from_cli and not set(combined) & set(targets_from_cli): - return True - return False - - -def get_targets_to_run(targets, targets_from_cli): - slow_targets = [] - regular_targets = [] - for target_name, lines in targets.items(): - if to_skip_because_of_targets_parameters(target_name, lines, targets_from_cli): - continue - if to_skip_because_disabled(lines): - continue - if to_skip_because_unstable(lines) and target_name not in targets_from_cli: - continue - if is_slow(lines): - slow_targets.append(target_name) - else: - regular_targets.append(target_name) - return slow_targets, regular_targets - - -def build_up_batches(slow_targets, regular_targets, total_jobs): - remaining_jobs = max(total_jobs - len(slow_targets), 1) - # Slow targets is a list of slow targets so we need to wrap each entry in a - # list - batches = [[i] for i in slow_targets] - for x in range(remaining_jobs): - batch = regular_targets[x::remaining_jobs] - if batch: - batches.append(batch) - return batches - - -def build_result_struct(jobs, batches): - result = { - "data": { - "zuul": {"child_jobs": jobs[0 : len(batches)]}, - "child": {"targets_to_test": batches}, - } - } - return result - - -def get_args(sys_argv): - def raw_targets(string): - return [x for x in string.split(" ") if x != ""] - - parser = argparse.ArgumentParser() - parser.add_argument( - "-t", - "--targets", - help="list of targets for integration testing. example: 'ec2_tag ami_info ec2_instance'", - default=[], - type=raw_targets, - ) - parser.add_argument( - "-p", "--prefix", help="list of targets for integration testing", default="job_" - ) - parser.add_argument( - "-c", "--collection_path", help="path to the collection.", default=os.getcwd() - ) - - return parser.parse_args(sys_argv[1:]) - - -if __name__ == "__main__": - - args = get_args(sys.argv) - jobs = get_job_list(args.prefix, total_jobs) - - targets = get_all_targets(args.collection_path) - slow_targets, regular_targets = get_targets_to_run(targets, args.targets) - batches = build_up_batches(slow_targets, regular_targets, total_jobs) - result = build_result_struct(jobs, batches) - - print(json.dumps(result)) diff --git a/roles/ansible-test-splitter/files/test_list_changed_targets.py b/roles/ansible-test-splitter/files/test_list_changed_targets.py new file mode 100644 index 000000000..59da41bc0 --- /dev/null +++ b/roles/ansible-test-splitter/files/test_list_changed_targets.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 + +import pytest +import io +from pathlib import PosixPath +from unittest.mock import MagicMock +from list_changed_targets import ( + Collection, + ElGrandeSeparator, + WhatHaveChanged, + list_pyimport, + parse_args, + read_collection_name, +) + +my_module = """ +from ..module_utils.core import AnsibleAWSModule +from ipaddress import ipaddress +import time +import botocore.exceptions +""" + + +def test_read_collection_name(): + m_galaxy_file = MagicMock() + m_galaxy_file.open = lambda: io.BytesIO(b"name: b\nnamespace: a\n") + m_path = MagicMock() + m_path.__truediv__.return_value = m_galaxy_file + assert read_collection_name(m_path) == "a.b" + + +def test_list_pyimport(): + assert list(list_pyimport("amazon.aws", my_module)) == [ + "ansible_collections.amazon.aws.plugins.module_utils.core", + "ipaddress", + "time", + "botocore.exceptions", + ] + + +def test_what_changed_files(): + whc = WhatHaveChanged(PosixPath("a"), "b") + whc.collection_name = lambda: "a.b" + whc.changed_files = lambda: [ + PosixPath("tests/something"), + PosixPath("plugins/module_utils/core.py"), + PosixPath("plugins/modules/ec2.py"), + ] + assert list(whc.modules()) == [PosixPath("plugins/modules/ec2.py")] + assert list(whc.module_utils()) == [ + "ansible_collections.a.b.plugins.module_utils.core" + ] + + +def build_collection(aliases): + c = Collection(PosixPath("nowhere")) + m_c_path = MagicMock() + c.collection_path = m_c_path + m_c_path.glob.return_value = aliases + return c + + +def build_alias(name, text): + m_alias_file = MagicMock() + m_alias_file.read_text.return_value = text + m_alias_file.parent.name = name + return m_alias_file + + +def test_c_targets(): + c = build_collection([]) + assert list(c._targets()) == [] + + c = build_collection([build_alias("a", "ec2\n")]) + assert len(list(c._targets())) == 1 + assert list(c._targets())[0].name == "a" + assert list(c._targets())[0].is_alias_of("ec2") + + c = build_collection([build_alias("a", "#ec2\n")]) + assert len(list(c._targets())) == 1 + assert list(c._targets())[0].name == "a" + + +def test_c_disabled_unstable(): + c = Collection(PosixPath("nowhere")) + m_c_path = MagicMock() + c.collection_path = m_c_path + m_c_path.glob.return_value = [ + build_alias("a", "disabled\n"), + build_alias("b", "unstable\n"), + ] + + # all, we should ignore the disabled,unstable jobs + c.cover_all() + assert len(c.regular_targets_to_test()) == 0 + # if the module is targets, we continue to ignore the disabled + c.cover_module(PosixPath("plugins/modules/a.py")) + assert len(c.regular_targets_to_test()) == 0 + # but the unstable is ok + c.cover_module(PosixPath("plugins/modules/b.py")) + assert len(c.regular_targets_to_test()) == 1 + + +def test_c_slow_regular_targets(): + c = Collection(PosixPath("nowhere")) + m_c_path = MagicMock() + c.collection_path = m_c_path + + m_c_path.glob.return_value = [ + build_alias("tortue", "slow\nec2\n#s3\n"), + build_alias("lapin", "notslow\ncarrot\n\n"), + ] + c.cover_all() + assert len(list(c._targets())) == 2 + assert list(c._targets())[0].is_slow() + assert not list(c._targets())[1].is_slow() + print(c.slow_targets_to_test()) + assert len(c.slow_targets_to_test()) == 1 + + +def test_c_with_cover(): + c = Collection(PosixPath("nowhere")) + m_c_path = MagicMock() + c.collection_path = m_c_path + + m_c_path.glob.return_value = [ + build_alias("tortue", "slow\nec2\n#s3\n"), + build_alias("lapin", "carrot\n\n"), + ] + c.cover_module(PosixPath("plugins/modules/ec2.py")) + assert len(c.slow_targets_to_test()) == 1 + assert c.regular_targets_to_test() == [] + + +def test_argparse(): + args = parse_args("--test-changed somewhere somewhere-else".split(" ")) + assert args.collection_to_tests == [ + PosixPath("somewhere"), + PosixPath("somewhere-else"), + ] + + +def test_splitter_basic(): + c = build_collection([build_alias("a", "ec2\n")]) + egs = ElGrandeSeparator(c) + with pytest.raises(StopIteration): + first = next(egs.build_up_batches(["slot1"], c)) + assert first == ("slot1", ["ec2"]) + + +def test_splitter_with_slow(): + c = build_collection( + [ + build_alias("a", "ec2\n"), + build_alias("slow-bob", "slow\n"), + build_alias("slow-jim", "slow\n"), + build_alias("regular-dude", "\n"), + ] + ) + c.cover_all() + egs = ElGrandeSeparator(c) + result = list(egs.build_up_batches([f"slot{i}" for i in range(4)], c)) + assert result == [ + ("slot0", ["slow-bob"]), + ("slot1", ["slow-jim"]), + ("slot2", ["a", "regular-dude"]), + ] diff --git a/roles/ansible-test-splitter/files/test_split_targets.py b/roles/ansible-test-splitter/files/test_split_targets.py deleted file mode 100644 index 0cd234b8a..000000000 --- a/roles/ansible-test-splitter/files/test_split_targets.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python - -from pathlib import PosixPath -import pytest -from unittest.mock import Mock, MagicMock, patch - -from split_targets import ( - get_job_list, - to_skip_because_disabled, - to_skip_because_unstable, - is_slow, - get_all_targets, - get_args, - to_skip_because_of_targets_parameters, - build_up_batches, - build_result_struct, - get_targets_to_run, -) - - -def new_aliases(content): - m_aliases = Mock() - m_aliases.is_file.return_value = True - m_aliases.read_text.return_value = content - return m_aliases - - -def new_target(name, content): - m_target = MagicMock(return_value=True) - m_target.is_dir.return_value = True - m_target.__truediv__.return_value = new_aliases(content) - m_target.name = name - return m_target - - -target_dir = [ - new_target("t1", "cloud/aws\nslow"), - new_target("t2", "t1"), - new_target("t3", "cloud/aws\n\ndisabled"), -] - - -def test_get_job_list(): - assert get_job_list("my_jobs", 4) == [ - "my_jobs0", - "my_jobs1", - "my_jobs2", - "my_jobs3", - ] - - -def test_to_skip(): - assert to_skip_because_disabled(["slow", "# disabled", "no_unstable"]) is False - assert to_skip_because_disabled(["# really unstable", "unstable"]) is False - assert to_skip_because_unstable(["slow", "# disabled", "no_unstable"]) is False - assert to_skip_because_unstable(["# really unstable", "unstable"]) is True - - -def test_is_slow(): - assert is_slow(["slow", "# disabled", "no_unstable"]) is True - assert is_slow(["noslow", "# reason: slow"]) is True - assert is_slow(["noslow", "# reason: noslow"]) is False - - -@patch.object(PosixPath, "glob", MagicMock(return_value=target_dir)) -def test_get_all_targets(): - assert get_all_targets("somewhere") == { - "t1": ["cloud/aws", "slow"], - "t2": ["t1"], - "t3": ["cloud/aws", "", "disabled"], - } - - -def test_get_args_targets_with_parameters(): - args = get_args([None, "-t", "foo bar", "-p", "my_prefix", "-c", "my_collection"]) - assert args.targets == ["foo", "bar"] - assert args.prefix == "my_prefix" - assert args.collection_path == "my_collection" - - -def test_get_args_targets_naked(): - with patch("os.getcwd", return_value="here"): - args = get_args([None]) - assert args.targets == [] - assert args.prefix == "job_" - assert args.collection_path == "here" - - -@pytest.mark.parametrize( - "target,lines,targets_from_cli,expected", - [ - ("ec2_eni", [], ["ec2_vol"], True), - ("ec2_eni", [], ["ec2_eni"], False), - ("ec2_eni", [], [], False), - ("ec2_eni", ["ec2_vol"], ["ec2_vol"], False), - ], -) -def test_to_skip_because_of_targets_parameters_skip( - target, lines, targets_from_cli, expected -): - assert ( - to_skip_because_of_targets_parameters(target, lines, targets_from_cli) - == expected - ) - - -@pytest.mark.parametrize( - "slow_targets,regular_targets,total_jobs,expected", - [ - ([], [], 6, []), - (["slow1", "slow2"], [], 6, [["slow1"], ["slow2"]]), - # NOTE: this situation should be improved. It don't make any - # sence to start 1 target per job. We should instead group the - # two targets. - ([], ["reg1", "reg2"], 6, [["reg1"], ["reg2"]]), - ( - [], - [f"reg{i}" for i in range(100)], - 3, - [ - [f"reg{i}" for i in range(0, 100, 3)], - [f"reg{i}" for i in range(1, 100, 3)], - [f"reg{i}" for i in range(2, 100, 3)], - ], - ), - ], -) -def test_build_up_batches(slow_targets, regular_targets, total_jobs, expected): - assert build_up_batches(slow_targets, regular_targets, total_jobs) == expected - - -@pytest.mark.parametrize( - "jobs,batches,expected", - [ - ( - [], - [], - {"data": {"zuul": {"child_jobs": []}, "child": {"targets_to_test": []}}}, - ), - ( - ["job_1", "job_2"], - [["a1", "b1", "c2"], ["b1", "c1", "c3"]], - { - "data": { - "zuul": {"child_jobs": ["job_1", "job_2"]}, - "child": { - "targets_to_test": [["a1", "b1", "c2"], ["b1", "c1", "c3"]] - }, - } - }, - ), - ], -) -def test_build_result_struct(jobs, batches, expected): - assert build_result_struct(jobs, batches) == expected - - -@pytest.mark.parametrize( - "target,targets_from_cli,expected", - [ - ({"test1": ["cloud/aws"]}, [], ([], ["test1"])), - ({"test1": ["cloud/aws", "slow"]}, [], (["test1"], [])), - ({"test1": ["cloud/aws"]}, ["test1"], ([], ["test1"])), - ({"test1": ["cloud/aws", "unstable"]}, ["test1"], ([], ["test1"])), - ({"test1": ["cloud/aws", "slow"]}, ["test1"], (["test1"], [])), - ], -) -def test_get_targets_to_run(target, targets_from_cli, expected): - assert get_targets_to_run(target, targets_from_cli) == expected diff --git a/roles/ansible-test-splitter/tasks/ansible_test_changed.yaml b/roles/ansible-test-splitter/tasks/ansible_test_changed.yaml deleted file mode 100644 index 5099e0847..000000000 --- a/roles/ansible-test-splitter/tasks/ansible_test_changed.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -- copy: - src: list_changed_targets.py - dest: /tmp/list_changed_targets.py - mode: '0700' - -- name: Identify the changed targets - command: python3 /tmp/list_changed_targets.py "{{ zuul.branch }}" - args: - chdir: "{{ ansible_test_location }}" - register: _result - -- set_fact: - ansible_test_splitter__targets_to_test: "{{ _result.stdout }}" diff --git a/roles/ansible-test-splitter/tasks/list_all.yaml b/roles/ansible-test-splitter/tasks/list_all.yaml deleted file mode 100644 index 7798a72e7..000000000 --- a/roles/ansible-test-splitter/tasks/list_all.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -- copy: - src: list_all.py - dest: /tmp/list_all.py - mode: '0700' - -- name: Identify all the targets - command: python3 /tmp/list_all.py - args: - chdir: "{{ ansible_test_location }}" - register: _result -- set_fact: - ansible_test_splitter__targets_to_test: "{{ _result.stdout }}" diff --git a/roles/ansible-test-splitter/tasks/main.yaml b/roles/ansible-test-splitter/tasks/main.yaml index 59b2c9b8a..4e765b34f 100644 --- a/roles/ansible-test-splitter/tasks/main.yaml +++ b/roles/ansible-test-splitter/tasks/main.yaml @@ -1,17 +1,24 @@ --- -- name: Identify the targets associated with the changed files - include_tasks: ansible_test_changed.yaml - when: ansible_test_splitter__test_changed|bool +- copy: + src: list_changed_targets.py + dest: /tmp/list_changed_targets.py + mode: '0700' -- debug: - msg: "Build a list of all the targets" -- name: Build a list of all the targets - include_tasks: list_all.yaml - when: not(ansible_test_splitter__test_changed|bool) - -- name: Print all available facts +- name: Will split up the jobs with the following command debug: - var: ansible_facts + msg: "python3 /tmp/list_changed_targets.py {% if ansible_test_splitter__test_changed|bool %}--test-changed{% else %}--test-all-the-targets{% endif %} {{ ansible_test_splitter__check_for_changes_in | join(' ') }}" + +- name: Split up the workload + command: python3 /tmp/list_changed_targets.py {% if ansible_test_splitter__test_changed|bool %}--test-changed{% else %}--test-all-the-targets{% endif %} {{ ansible_test_splitter__check_for_changes_in | join(' ') }} + register: _result + +- debug: var=_result + +- set_fact: + for_zuul_return: '{{ _result.stdout | from_json }}' + +- debug: var=for_zuul_return -- name: Split targets - include_tasks: split_targets.yaml +- name: Return the result to Zuul + zuul_return: + data: "{{ for_zuul_return.data }}" diff --git a/roles/ansible-test-splitter/tasks/split_targets.yaml b/roles/ansible-test-splitter/tasks/split_targets.yaml deleted file mode 100644 index 7c8100fe7..000000000 --- a/roles/ansible-test-splitter/tasks/split_targets.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -- copy: - src: split_targets.py - dest: /tmp/split_targets.py - mode: '0700' - -- name: Split the workload - command: python3 /tmp/split_targets.py -c "{{ ansible_test_location }}" -p "{{ ansible_test_splitter__children_prefix }}" -t "{{ ansible_test_splitter__targets_to_test|default('') }}" - register: _result -- debug: var=_result -- set_fact: - for_zuul_return: '{{ _result.stdout | from_json }}' -- debug: var=for_zuul_return -- name: Register the result - zuul_return: - data: "{{ for_zuul_return.data }}" diff --git a/zuul.d/ansible-cloud-jobs.yaml b/zuul.d/ansible-cloud-jobs.yaml index bc988320f..65a933475 100644 --- a/zuul.d/ansible-cloud-jobs.yaml +++ b/zuul.d/ansible-cloud-jobs.yaml @@ -280,7 +280,7 @@ max: 6 - job: - name: ansible-test-cloud-integration-aws-py38 + name: ansible-test-cloud-integration-aws parent: ansible-core-ci-aws-session nodeset: controller-python38 dependencies: @@ -317,40 +317,173 @@ semaphore: ansible-test-cloud-integration-aws - job: - name: ansible-test-cloud-integration-aws-py38_0 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-amazon.aws-1 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-2 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-3 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-4 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-5 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-6 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-7 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-8 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-9 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-10 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-11 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-12 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-amazon.aws-13 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + +- job: + name: integration-community.aws-1 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-2 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-3 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-4 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-5 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-6 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-7 + parent: ansible-test-cloud-integration-aws + vars: + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws + +- job: + name: integration-community.aws-8 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[0]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws - job: - name: ansible-test-cloud-integration-aws-py38_1 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-community.aws-9 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[1]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws - job: - name: ansible-test-cloud-integration-aws-py38_2 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-community.aws-10 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[2]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws - job: - name: ansible-test-cloud-integration-aws-py38_3 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-community.aws-11 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[3]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws - job: - name: ansible-test-cloud-integration-aws-py38_4 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-community.aws-12 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[4]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws - job: - name: ansible-test-cloud-integration-aws-py38_5 - parent: ansible-test-cloud-integration-aws-py38 + name: integration-community.aws-13 + parent: ansible-test-cloud-integration-aws vars: - ansible_test_integration_targets: "{{ child.targets_to_test[5]|join(' ') }}" + ansible_test_integration_targets: "{{ child.targets_to_test[zuul.job] }}" + ansible_collections_repo: github.com/ansible-collections/community.aws #### units diff --git a/zuul.d/project-templates.yaml b/zuul.d/project-templates.yaml index 8c6c3ae5e..54c1f0cbc 100644 --- a/zuul.d/project-templates.yaml +++ b/zuul.d/project-templates.yaml @@ -68,14 +68,39 @@ - name: github.com/ansible-collections/community.general - name: github.com/ansible-collections/community.crypto - ansible-test-splitter: + required-projects: + - name: github.com/ansible-collections/community.aws vars: ansible_test_splitter__test_changed: true - - ansible-test-cloud-integration-aws-py38_0 - - ansible-test-cloud-integration-aws-py38_1 - - ansible-test-cloud-integration-aws-py38_2 - - ansible-test-cloud-integration-aws-py38_3 - - ansible-test-cloud-integration-aws-py38_4 - - ansible-test-cloud-integration-aws-py38_5 + ansible_test_splitter__check_for_changes_in: + - "~/{{ zuul.projects['github.com/ansible-collections/amazon.aws'].src_dir }}" + - "~/{{ zuul.projects['github.com/ansible-collections/community.aws'].src_dir }}" + - integration-amazon.aws-1 + - integration-amazon.aws-2 + - integration-amazon.aws-3 + - integration-amazon.aws-4 + - integration-amazon.aws-5 + - integration-amazon.aws-6 + - integration-amazon.aws-7 + - integration-amazon.aws-8 + - integration-amazon.aws-9 + - integration-amazon.aws-10 + - integration-amazon.aws-11 + - integration-amazon.aws-12 + - integration-amazon.aws-13 + - integration-community.aws-1 + - integration-community.aws-2 + - integration-community.aws-3 + - integration-community.aws-4 + - integration-community.aws-5 + - integration-community.aws-6 + - integration-community.aws-7 + - integration-community.aws-8 + - integration-community.aws-9 + - integration-community.aws-10 + - integration-community.aws-11 + - integration-community.aws-12 + - integration-community.aws-13 gate: queue: integrated-aws jobs: *ansible-collections-amazon-aws-jobs @@ -91,12 +116,18 @@ - ansible-test-splitter: vars: ansible_test_splitter__test_changed: false - - ansible-test-cloud-integration-aws-py38_0 - - ansible-test-cloud-integration-aws-py38_1 - - ansible-test-cloud-integration-aws-py38_2 - - ansible-test-cloud-integration-aws-py38_3 - - ansible-test-cloud-integration-aws-py38_4 - - ansible-test-cloud-integration-aws-py38_5 + ansible_test_splitter__check_for_changes_in: + - "~/{{ zuul.projects['github.com/ansible-collections/amazon.aws'].src_dir }}" + - integration-amazon.aws-1 + - integration-amazon.aws-2 + - integration-amazon.aws-3 + - integration-amazon.aws-4 + - integration-amazon.aws-5 + - integration-amazon.aws-6 + - integration-amazon.aws-7 + - integration-amazon.aws-8 + - integration-amazon.aws-9 + - integration-amazon.aws-10 - project-template: name: ansible-collections-community-aws @@ -119,12 +150,21 @@ - ansible-test-splitter: vars: ansible_test_splitter__test_changed: true - - ansible-test-cloud-integration-aws-py38_0 - - ansible-test-cloud-integration-aws-py38_1 - - ansible-test-cloud-integration-aws-py38_2 - - ansible-test-cloud-integration-aws-py38_3 - - ansible-test-cloud-integration-aws-py38_4 - - ansible-test-cloud-integration-aws-py38_5 + ansible_test_splitter__check_for_changes_in: + - "~/{{ zuul.projects['github.com/ansible-collections/community.aws'].src_dir }}" + - integration-community.aws-1 + - integration-community.aws-2 + - integration-community.aws-3 + - integration-community.aws-4 + - integration-community.aws-5 + - integration-community.aws-6 + - integration-community.aws-7 + - integration-community.aws-8 + - integration-community.aws-9 + - integration-community.aws-10 + - integration-community.aws-11 + - integration-community.aws-12 + - integration-community.aws-13 - ansible-galaxy-importer: voting: false third-party-check: @@ -146,12 +186,18 @@ - ansible-test-splitter: vars: ansible_test_splitter__test_changed: false - - ansible-test-cloud-integration-aws-py38_0 - - ansible-test-cloud-integration-aws-py38_1 - - ansible-test-cloud-integration-aws-py38_2 - - ansible-test-cloud-integration-aws-py38_3 - - ansible-test-cloud-integration-aws-py38_4 - - ansible-test-cloud-integration-aws-py38_5 + ansible_test_splitter__check_for_changes_in: + - "~/{{ zuul.projects['github.com/ansible-collections/community.aws'].src_dir }}" + - integration-community.aws-1 + - integration-community.aws-2 + - integration-community.aws-3 + - integration-community.aws-4 + - integration-community.aws-5 + - integration-community.aws-6 + - integration-community.aws-7 + - integration-community.aws-8 + - integration-community.aws-9 + - integration-community.aws-10 - ansible-galaxy-importer: voting: false