Skip to content

Commit 1d3e5c7

Browse files
committed
Cancel Koji builds before starting new ones
The implementation does not rely on `commit_sha_before`, which is not set for some events (such as PR comments, check reruns, Pagure events, etc.). This patch uses project event type + event_id to identify all obsolete builds instead. Otherwise it is similar to what's already present for Copr builds. For: #2989 Signed-off-by: Marek Blaha <mblaha@redhat.com>
1 parent ec61cff commit 1d3e5c7

File tree

8 files changed

+479
-50
lines changed

8 files changed

+479
-50
lines changed

packit_service/models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2626,6 +2626,34 @@ def create(cls, run_model: "PipelineModel") -> "KojiBuildGroupModel":
26262626
session.add(run_model)
26272627
return build_group
26282628

2629+
@classmethod
2630+
def get_running(
2631+
cls,
2632+
project_event_type: ProjectEventModelType,
2633+
event_id: int,
2634+
) -> Iterable["KojiBuildTargetModel"]:
2635+
"""Get running Koji builds for a given project object (e.g. a PR or branch).
2636+
2637+
Args:
2638+
project_event_type: Type of the project event (e.g. pull_request).
2639+
event_id: ID of the project object (e.g. PullRequestModel.id).
2640+
2641+
Returns:
2642+
An iterable over KojiBuildTargetModels in non-final states.
2643+
"""
2644+
with sa_session_transaction() as session:
2645+
return (
2646+
session.query(KojiBuildTargetModel)
2647+
.join(KojiBuildGroupModel)
2648+
.join(PipelineModel)
2649+
.join(ProjectEventModel)
2650+
.filter(
2651+
ProjectEventModel.type == project_event_type,
2652+
ProjectEventModel.event_id == event_id,
2653+
KojiBuildTargetModel.status.in_(("pending", "queued", "running")),
2654+
)
2655+
)
2656+
26292657

26302658
class BodhiUpdateTargetModel(GroupAndTargetModelConnector, Base):
26312659
__tablename__ = "bodhi_update_targets"

packit_service/worker/handlers/distgit.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,9 @@ def report(self, description: str, commit_status: BaseCommitStatus, url: Optiona
884884
)
885885

886886
def _run(self) -> TaskResults:
887+
if getenv("CANCEL_RUNNING_JOBS"):
888+
self.koji_build_helper.cancel_running_builds()
889+
887890
try:
888891
self.packit_api.init_kerberos_ticket()
889892
except PackitCommandFailedError as ex:
@@ -1105,6 +1108,9 @@ def is_already_triggered(self, nvr: str) -> bool:
11051108
return False
11061109

11071110
def _run(self) -> TaskResults:
1111+
if getenv("CANCEL_RUNNING_JOBS"):
1112+
self.koji_build_helper.cancel_running_builds()
1113+
11081114
try:
11091115
group = self._get_or_create_koji_group_model()
11101116
except PackitException as ex:

packit_service/worker/handlers/koji.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ def get_checkers() -> tuple[type[Checker], ...]:
124124
)
125125

126126
def _run(self) -> TaskResults:
127+
if getenv("CANCEL_RUNNING_JOBS"):
128+
self.koji_build_helper.cancel_running_builds()
129+
127130
return self.koji_build_helper.run_koji_build()
128131

129132

packit_service/worker/helpers/build/koji_build.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
import logging
55
from collections.abc import Iterable
6-
from typing import Any, Optional
6+
from typing import Optional
77

88
from ogr.abstract import GitProject
99
from packit.config import JobConfig, JobType
1010
from packit.config.aliases import get_all_koji_targets, get_koji_targets
1111
from packit.config.package_config import PackageConfig
1212
from packit.exceptions import PackitCommandFailedError
13+
from packit.utils.koji_helper import KojiHelper
1314

1415
from packit_service import sentry_integration
1516
from packit_service.config import ServiceConfig
@@ -227,6 +228,24 @@ def run_build(
227228

228229
return get_koji_task_id_and_url_from_stdout(out)
229230

230-
# [TODO] Switch from ‹Any› to the correct type when implementing
231-
def get_running_jobs(self) -> Iterable[Any]:
232-
raise NotImplementedError("See https://github.com/packit/packit/issues/2535")
231+
def get_running_jobs(self) -> Iterable[KojiBuildTargetModel]:
232+
return KojiBuildGroupModel.get_running(
233+
project_event_type=self.db_project_event.type,
234+
event_id=self.db_project_event.event_id,
235+
)
236+
237+
def cancel_running_builds(self) -> None:
238+
"""Cancel running Koji builds for the current project object
239+
(e.g. a PR or branch).
240+
"""
241+
running_builds = list(self.get_running_jobs())
242+
if not running_builds:
243+
logger.info("No running Koji builds to cancel.")
244+
return
245+
246+
koji_helper = KojiHelper()
247+
for build in running_builds:
248+
if build.task_id is not None:
249+
logger.debug(f"Cancelling Koji task {build.task_id}")
250+
koji_helper.cancel_task(int(build.task_id))
251+
build.set_status(BuildStatus.canceled)

tests/integration/conftest.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Copyright Contributors to the Packit project.
2+
# SPDX-License-Identifier: MIT
3+
4+
import pytest
5+
from flexmock import flexmock
6+
from github.MainClass import Github
7+
from ogr.services.github import GithubProject
8+
from packit.config import JobConfigTriggerType
9+
from packit.local_project import LocalProject
10+
11+
from packit_service.models import (
12+
ProjectEventModel,
13+
PullRequestModel,
14+
)
15+
16+
17+
@pytest.fixture
18+
def mock_pr_functionality(request):
19+
packit_yaml = "{'specfile_path': 'the-specfile.spec', 'jobs':" + str(request.param) + "}"
20+
flexmock(
21+
GithubProject,
22+
full_repo_name="packit/hello-world",
23+
get_file_content=lambda path, ref, headers: packit_yaml,
24+
get_files=lambda ref, filter_regex: ["the-specfile.spec"],
25+
get_web_url=lambda: "https://github.com/the-namespace/the-repo",
26+
get_pr=lambda pr_id: flexmock(head_commit="12345"),
27+
)
28+
flexmock(Github, get_repo=lambda full_name_or_id: None)
29+
30+
pr_model = (
31+
flexmock(PullRequestModel(pr_id=123))
32+
.should_receive("get_project_event_models")
33+
.and_return([flexmock(commit_sha="12345")])
34+
.mock()
35+
)
36+
project_event = (
37+
flexmock(ProjectEventModel(type=JobConfigTriggerType.pull_request, id=123456))
38+
.should_receive("get_project_event_object")
39+
.and_return(pr_model)
40+
.mock()
41+
.should_receive("set_packages_config")
42+
.mock()
43+
)
44+
flexmock(LocalProject, refresh_the_arguments=lambda: None)
45+
flexmock(ProjectEventModel).should_receive("get_or_create").and_return(
46+
project_event,
47+
)
48+
flexmock(ProjectEventModel).should_receive("get_by_id").with_args(
49+
123456,
50+
).and_return(project_event)
51+
flexmock(PullRequestModel).should_receive("get_or_create").with_args(
52+
pr_id=123,
53+
namespace="packit",
54+
repo_name="hello-world",
55+
project_url="https://github.com/packit/hello-world",
56+
).and_return(pr_model)
57+
flexmock(PullRequestModel).should_receive("get_by_id").with_args(123).and_return(
58+
pr_model,
59+
)

tests/integration/test_check_rerun.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
GitBranchModel,
2121
ProjectEventModel,
2222
ProjectReleaseModel,
23-
PullRequestModel,
2423
TestingFarmResult,
2524
TFTTestRunGroupModel,
2625
TFTTestRunTargetModel,
@@ -88,51 +87,6 @@ def check_rerun_event_propose_downstream():
8887
return event
8988

9089

91-
@pytest.fixture
92-
def mock_pr_functionality(request):
93-
packit_yaml = "{'specfile_path': 'the-specfile.spec', 'jobs':" + str(request.param) + "}"
94-
flexmock(
95-
GithubProject,
96-
full_repo_name="packit/hello-world",
97-
get_file_content=lambda path, ref, headers: packit_yaml,
98-
get_files=lambda ref, filter_regex: ["the-specfile.spec"],
99-
get_web_url=lambda: "https://github.com/the-namespace/the-repo",
100-
get_pr=lambda pr_id: flexmock(head_commit="12345"),
101-
)
102-
flexmock(Github, get_repo=lambda full_name_or_id: None)
103-
104-
pr_model = (
105-
flexmock(PullRequestModel(pr_id=123))
106-
.should_receive("get_project_event_models")
107-
.and_return([flexmock(commit_sha="12345")])
108-
.mock()
109-
)
110-
project_event = (
111-
flexmock(ProjectEventModel(type=JobConfigTriggerType.pull_request, id=123456))
112-
.should_receive("get_project_event_object")
113-
.and_return(pr_model)
114-
.mock()
115-
.should_receive("set_packages_config")
116-
.mock()
117-
)
118-
flexmock(LocalProject, refresh_the_arguments=lambda: None)
119-
flexmock(ProjectEventModel).should_receive("get_or_create").and_return(
120-
project_event,
121-
)
122-
flexmock(ProjectEventModel).should_receive("get_by_id").with_args(
123-
123456,
124-
).and_return(project_event)
125-
flexmock(PullRequestModel).should_receive("get_or_create").with_args(
126-
pr_id=123,
127-
namespace="packit",
128-
repo_name="hello-world",
129-
project_url="https://github.com/packit/hello-world",
130-
).and_return(pr_model)
131-
flexmock(PullRequestModel).should_receive("get_by_id").with_args(123).and_return(
132-
pr_model,
133-
)
134-
135-
13690
@pytest.fixture
13791
def mock_push_functionality(request):
13892
packit_yaml = "{'specfile_path': 'the-specfile.spec', 'jobs':" + str(request.param) + "}"

0 commit comments

Comments
 (0)