Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packit_service/events/koji/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def __init__(
old_state: Optional[KojiTaskState] = None,
rpm_build_task_ids: Optional[dict[str, int]] = None,
rpm_build_failed_arch_list: Optional[list[str]] = None,
rpm_build_task_labels: Optional[dict[str, str]] = None,
start_time: Optional[Union[int, float, str]] = None,
completion_time: Optional[Union[int, float, str]] = None,
):
Expand All @@ -164,6 +165,7 @@ def __init__(
completion_time=completion_time,
)
self.rpm_build_failed_arch_list = rpm_build_failed_arch_list
self.rpm_build_task_labels = rpm_build_task_labels or {}
self.state = state
self.old_state = old_state

Expand Down Expand Up @@ -227,6 +229,7 @@ def from_event_dict(cls, event: dict) -> "Task":
old_state=(KojiTaskState(event.get("old_state")) if event.get("old_state") else None),
rpm_build_task_ids=event.get("rpm_build_task_ids"),
rpm_build_failed_arch_list=event.get("rpm_build_failed_arch_list"),
rpm_build_task_labels=event.get("rpm_build_task_labels"),
start_time=event.get("start_time"),
completion_time=event.get("completion_time"),
)
Expand Down
14 changes: 13 additions & 1 deletion packit_service/worker/helpers/logdetective.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,22 @@ def trigger_log_detective_analysis_for_arch(self, arch: str) -> bool:
# "target" field in LDRunModel refers to:
# - "target-arch" for Koji builds (e.g. fc44-aarch64)
# - "chroot" for Copr builds (e.g. fedora-rawhide-x86_64)

# Note: Parent tasks for scratch builds of both arch-sensitive and arch-agnostic (noarch)
# packages have arch=="noarch".
# - A) In case of noarch packages, child buildArch subtasks have "noarch" label.
# So parent task: arch=noarch, child buildArch task arch=whatever, but label=noarch.
# - B) In case of arch-sensitive buildArch subtasks, label is the same as arch.
# Since koji can give builders with different arches for noarch packages when retriggering,
# we have to use label to ensure that the new analysis CI job replaces the old one.

status_arch = (
"noarch" if self.koji_event.rpm_build_task_labels.get(arch) == "noarch" else arch
)
LogDetectiveRunModel.create(
LogDetectiveResult.running,
str(build_arch_task_id),
f"{self.koji_event.target}-{arch}",
f"{self.koji_event.target}-{status_arch}",
LogDetectiveBuildSystem.koji,
analysis_id,
self.run_group,
Expand Down
3 changes: 3 additions & 0 deletions packit_service/worker/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1458,12 +1458,14 @@ def parse_koji_task_event(event) -> Optional[koji.result.Task]:

rpm_build_task_ids = {}
rpm_build_failed_arch_list: list[str] = []
rpm_build_task_labels = {}
for children in nested_get(event, "info", "children", default=[]):
arch = children.get("arch")
subtask_id = children.get("id")
subtask_state = children.get("state")
if children.get("method") == "buildArch":
rpm_build_task_ids[arch] = subtask_id
rpm_build_task_labels[arch] = children.get("label")
if KojiTaskState.from_number(subtask_state) == KojiTaskState.failed:
rpm_build_failed_arch_list.append(arch)

Expand All @@ -1475,6 +1477,7 @@ def parse_koji_task_event(event) -> Optional[koji.result.Task]:
completion_time=completion_time,
rpm_build_task_ids=rpm_build_task_ids,
rpm_build_failed_arch_list=rpm_build_failed_arch_list,
rpm_build_task_labels=rpm_build_task_labels,
)

@staticmethod
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/events/test_koji.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def test_parse_koji_build_scratch_event_end(koji_build_scratch_end, koji_build_p
assert event_object.task_id == 45270170
assert event_object.state == KojiTaskState.closed
assert event_object.rpm_build_task_ids == {"noarch": 45270227}
assert event_object.rpm_build_task_labels == {"noarch": "noarch"}
assert event_object.get_koji_build_rpm_tasks_logs_urls() == {
"noarch": "https://kojipkgs.fedoraproject.org//work/tasks/227/45270227/mock_output.log",
}
Expand All @@ -50,6 +51,23 @@ def test_parse_koji_build_scratch_event_end(koji_build_scratch_end, koji_build_p
assert event_object.project.full_repo_name == "foo/bar"


def test_parse_koji_build_scratch_event_preserves_noarch_label(
koji_build_scratch_end,
):
"""
This is related to the broken reporting of Log Detective jobs for noarch packages.
Koji could provision different arches for different LD runs, posting 2 seemingly
different analyses, when in fact they should be replaced. The parser for koji events
had to also store labels for that reason.
"""
koji_build_scratch_end["info"]["children"][1]["arch"] = "x86_64"

event_object = Parser.parse_koji_task_event(koji_build_scratch_end)

assert event_object.rpm_build_task_ids == {"x86_64": 45270227}
assert event_object.rpm_build_task_labels == {"x86_64": "noarch"}


def test_parse_koji_build_event_start_old_format(
koji_build_start_old_format,
mock_config,
Expand Down
46 changes: 46 additions & 0 deletions tests/unit/test_logdetective_koji_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def mock_koji_task_failed_event():
build_model=mock_build_model,
rpm_build_task_ids={"x86_64": 12345},
rpm_build_failed_arch_list=["x86_64"],
rpm_build_task_labels={"x86_64": "x86_64"},
db_project_object=None,
start_time=1000,
completion_time=1045,
Expand Down Expand Up @@ -221,6 +222,51 @@ def test_logdetective_koji_success(
assert all(trigger_success_list)


def test_logdetective_koji_noarch_build_uses_noarch_status_target(
mock_koji_task_failed_event, mock_event_data, mock_pushgateway_log_detective_inc
):
mock_response = flexmock(status_code=200)
mock_response.should_receive("raise_for_status")
mock_response.should_receive("json").and_return(
{
"log_detective_analysis_id": "test-uuid-123",
"creation_time": "2026-01-01T12:00:00",
}
)
mock_koji_task_failed_event.rpm_build_task_labels = {"x86_64": "noarch"}
flexmock(logdetective_module).should_receive("verify_artifact").and_return(True)
flexmock(requests).should_receive("post").once().and_return(mock_response)

mock_group_run = flexmock()
flexmock(LogDetectiveRunGroupModel).should_receive("create").once().and_return(mock_group_run)
flexmock(LogDetectiveRunModel).should_receive("create").with_args(
LogDetectiveResult.running,
"12345",
"rawhide-noarch",
LogDetectiveBuildSystem.koji,
"test-uuid-123",
mock_group_run,
).once()
mock_koji_task_failed_event.build_model.should_receive("add_log_detective_run").with_args(
"test-uuid-123"
).once()
flexmock(logger).should_receive("info").with_args(
"Triggered Log Detective for a failed Koji build "
"(child taskID = 12345, arch = x86_64, trigger = success)"
)

helper = LogDetectiveKojiTriggerHelper(
mock_koji_task_failed_event,
mock_event_data,
mock_pushgateway_log_detective_inc,
"https://kojipkgs.fedoraproject.org",
LOGDETECTIVE_PACKIT_SERVER_URL,
"secret-123",
)

assert helper.trigger_log_detective_analysis() == [True]


def test_logdetective_koji_http_error(
mock_koji_task_failed_event, mock_event_data, mock_pushgateway_log_detective_no_inc
):
Expand Down
Loading