Skip to content

Commit 0905dd1

Browse files
committed
kpd: add optional mirror support
We always clone a full repository. This is counter productive and wasteful. Allow users to specify that they are using kpd to help test patches for a git tree which we should expect a mirror on a target mirror path. We simply check for a mirror for you git tree and if present we use it. So for example, all kdevops enterprise deployments can easily profit from this as kdevops has support to mirror all target git trees it supports under /mirror/ through an NFS export for clients. And so small thing guests can be used for kpd instances, which can leverage this NFS export. This allows kpd to be run on smaller guests with less storage needs. This should allow more than one kpd instance to run on small guests too. Generated-by: Claude AI Signed-off-by: Luis Chamberlain <[email protected]> Signed-off-by: Daniel Gomez <[email protected]>
1 parent ef6131d commit 0905dd1

File tree

7 files changed

+126
-4
lines changed

7 files changed

+126
-4
lines changed

configs/kpd.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@
3030
"upstream_branch": "master",
3131
"ci_repo": "https://github.com/kernel-patches/vmtest.git",
3232
"ci_branch": "master",
33-
"github_oauth_token": "<TOKEN>"
33+
"github_oauth_token": "<TOKEN>",
34+
"mirror_fallback_repo": "linux.git"
3435
},
3536
"bpf": {
3637
"repo": "https://github.com/kernel-patches/bpf",
3738
"upstream": "https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git",
3839
"upstream_branch": "master",
3940
"ci_repo": "https://github.com/kernel-patches/vmtest.git",
4041
"ci_branch": "master",
41-
"github_oauth_token": "<TOKEN>"
42+
"github_oauth_token": "<TOKEN>",
43+
"mirror_fallback_repo": "linux.git"
4244
}
4345
},
44-
"base_directory": "/tmp/repos"
46+
"base_directory": "/tmp/repos",
47+
"mirror_dir": "/mirror/"
4548
}

kernel_patches_daemon/branch_worker.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,8 @@ def __init__(
664664
app_auth: Optional[Auth.AppInstallationAuth] = None,
665665
email: Optional[EmailConfig] = None,
666666
http_retries: Optional[int] = None,
667+
mirror_fallback_repo: Optional[str] = None,
668+
mirror_dir: Optional[str] = None,
667669
) -> None:
668670
super().__init__(
669671
repo_url=repo_url,
@@ -676,6 +678,8 @@ def __init__(
676678
self.email_config = email
677679

678680
self.log_extractor = log_extractor
681+
self.mirror_dir = mirror_dir
682+
self.mirror_fallback_repo = mirror_fallback_repo
679683
self.ci_repo_url = ci_repo_url
680684
self.ci_repo_dir = _uniq_tmp_folder(ci_repo_url, ci_branch, base_directory)
681685
self.ci_branch = ci_branch
@@ -810,9 +814,27 @@ def do_sync(self) -> None:
810814
def full_sync(self, path: str, url: str, branch: str) -> git.Repo:
811815
logging.info(f"Doing full clone from {redact_url(url)}, branch: {branch}")
812816

817+
multi_opts: Optional[List[str]] = None
818+
if self.mirror_dir:
819+
upstream_name = os.path.basename(self.upstream_url)
820+
reference_path = os.path.join(self.mirror_dir, upstream_name)
821+
822+
# If primary mirror doesn't exist, try fallback path
823+
if not os.path.exists(reference_path) and self.mirror_fallback_repo:
824+
fallback_path = os.path.join(self.mirror_dir, self.mirror_fallback_repo)
825+
if os.path.exists(fallback_path):
826+
reference_path = fallback_path
827+
828+
# Use --reference-if-able when mirror path exists
829+
if os.path.exists(reference_path):
830+
multi_opts = ["--reference-if-able", reference_path]
831+
813832
with HistogramMetricTimer(git_clone_duration, {"branch": branch}):
814833
shutil.rmtree(path, ignore_errors=True)
815-
repo = git.Repo.clone_from(url, path)
834+
if multi_opts:
835+
repo = git.Repo.clone_from(url, path, multi_options=multi_opts)
836+
else:
837+
repo = git.Repo.clone_from(url, path)
816838
_reset_repo(repo, f"origin/{branch}")
817839

818840
git_clone_counter.add(1, {"branch": branch})

kernel_patches_daemon/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class BranchConfig:
8383
ci_branch: str
8484
github_oauth_token: Optional[str]
8585
github_app_auth: Optional[GithubAppAuthConfig]
86+
mirror_fallback_repo: Optional[str] = None
8687

8788
@classmethod
8889
def from_json(cls, json: Dict) -> "BranchConfig":
@@ -101,6 +102,7 @@ def from_json(cls, json: Dict) -> "BranchConfig":
101102
ci_branch=json["ci_branch"],
102103
github_oauth_token=json.get("github_oauth_token", None),
103104
github_app_auth=github_app_auth_config,
105+
mirror_fallback_repo=json.get("mirror_fallback_repo", None),
104106
)
105107

106108

@@ -200,6 +202,7 @@ class KPDConfig:
200202
branches: Dict[str, BranchConfig]
201203
tag_to_branch_mapping: Dict[str, List[str]]
202204
base_directory: str
205+
mirror_dir: Optional[str] = None
203206

204207
@classmethod
205208
def from_json(cls, json: Dict) -> "KPDConfig":
@@ -232,6 +235,7 @@ def from_json(cls, json: Dict) -> "KPDConfig":
232235
for name, json_config in json["branches"].items()
233236
},
234237
base_directory=json["base_directory"],
238+
mirror_dir=json.get("mirror_dir"),
235239
)
236240

237241
@classmethod

kernel_patches_daemon/github_sync.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def __init__(
111111
ci_branch=branch_config.ci_branch,
112112
log_extractor=_log_extractor_from_project(kpd_config.patchwork.project),
113113
base_directory=kpd_config.base_directory,
114+
mirror_dir=kpd_config.mirror_dir,
115+
mirror_fallback_repo=branch_config.mirror_fallback_repo,
114116
http_retries=http_retries,
115117
github_oauth_token=branch_config.github_oauth_token,
116118
app_auth=github_app_auth_from_branch_config(branch_config),

tests/test_branch_worker.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
TEST_CI_REPO_URL = f"https://user:[email protected]/ci-org/{TEST_CI_REPO}"
8080
TEST_CI_BRANCH = "test_ci_branch"
8181
TEST_BASE_DIRECTORY = "/repos"
82+
TEST_MIRROR_DIRECTORY = "/mirror"
8283
TEST_BRANCH = "test-branch"
8384
TEST_CONFIG: Dict[str, Any] = {
8485
"version": 2,
@@ -135,6 +136,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
135136
"ci_branch": TEST_CI_BRANCH,
136137
"log_extractor": DefaultGithubLogExtractor(),
137138
"base_directory": TEST_BASE_DIRECTORY,
139+
"mirror_dir": None,
140+
"mirror_fallback_repo": None,
138141
}
139142
presets.update(kwargs)
140143

@@ -482,6 +485,69 @@ def test_fetch_repo_path_exists_git_exception(self) -> None:
482485
self._bw.fetch_repo(*fetch_params)
483486
fr.assert_called_once_with(*fetch_params)
484487

488+
def test_full_sync_with_mirror_dir(self) -> None:
489+
bw = BranchWorkerMock(mirror_dir=TEST_MIRROR_DIRECTORY)
490+
mirror_path = os.path.join(
491+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
492+
)
493+
with (
494+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
495+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
496+
):
497+
exists.side_effect = lambda p: p == mirror_path
498+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
499+
bw.full_sync("somepath", "giturl", "branch")
500+
self._git_repo_mock.clone_from.assert_called_once_with(
501+
"giturl",
502+
"somepath",
503+
multi_options=["--reference-if-able", mirror_path],
504+
)
505+
506+
def test_full_sync_with_mirror_fallback_repo(self) -> None:
507+
fallback_repo = "linux.git"
508+
bw = BranchWorkerMock(
509+
mirror_dir=TEST_MIRROR_DIRECTORY, mirror_fallback_repo=fallback_repo
510+
)
511+
primary_mirror_path = os.path.join(
512+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
513+
)
514+
fallback_mirror_path = os.path.join(TEST_MIRROR_DIRECTORY, fallback_repo)
515+
with (
516+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
517+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
518+
):
519+
# Primary mirror doesn't exist, but fallback does
520+
exists.side_effect = lambda p: p == fallback_mirror_path
521+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
522+
bw.full_sync("somepath", "giturl", "branch")
523+
# Should use fallback mirror path for reference
524+
self._git_repo_mock.clone_from.assert_called_once_with(
525+
"giturl",
526+
"somepath",
527+
multi_options=["--reference-if-able", fallback_mirror_path],
528+
)
529+
530+
def test_full_sync_without_mirror_fallback_repo(self) -> None:
531+
bw = BranchWorkerMock(
532+
mirror_dir=TEST_MIRROR_DIRECTORY, mirror_fallback_repo=None
533+
)
534+
mirror_path = os.path.join(
535+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
536+
)
537+
with (
538+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
539+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
540+
):
541+
# Mirror doesn't exist, and no fallback repo specified - should not use reference
542+
exists.side_effect = lambda p: False
543+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
544+
bw.full_sync("somepath", "giturl", "branch")
545+
# Should clone without reference
546+
self._git_repo_mock.clone_from.assert_called_once_with(
547+
"giturl",
548+
"somepath",
549+
)
550+
485551
def test_expire_branches(self) -> None:
486552
"""Only the branch that matches pattern and is expired should be deleted"""
487553
not_expired_time = datetime.fromtimestamp(3 * BRANCH_TTL)

tests/test_config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,19 @@ def test_valid(self) -> None:
215215
ci_branch="ci_branch",
216216
github_app_auth=None,
217217
github_oauth_token="TEST_OAUTH_TOKEN",
218+
mirror_fallback_repo=None,
218219
),
219220
},
220221
base_directory="/repos",
222+
mirror_dir=None,
221223
)
222224
self.assertEqual(config, expected_config)
225+
226+
def test_branch_mirror_fallback_repo(self) -> None:
227+
kpd_config_json = read_fixture("fixtures/kpd_config.json")
228+
kpd_config_json["branches"]["oauth"]["mirror_fallback_repo"] = "linux.git"
229+
230+
with patch("builtins.open", mock_open(read_data="TEST_KEY_FILE_CONTENT")):
231+
config = KPDConfig.from_json(kpd_config_json)
232+
233+
self.assertEqual(config.branches["oauth"].mirror_fallback_repo, "linux.git")

tests/test_github_sync.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ class TestCase:
127127
gh.workers[TEST_BRANCH].ci_repo_dir.startswith(case.prefix),
128128
)
129129

130+
def test_init_with_mirror_dir(self) -> None:
131+
config = copy.copy(TEST_CONFIG)
132+
config["mirror_dir"] = "/mirror"
133+
kpd_config = KPDConfig.from_json(config)
134+
gh = GithubSyncMock(kpd_config=kpd_config)
135+
self.assertEqual("/mirror", gh.workers[TEST_BRANCH].mirror_dir)
136+
137+
def test_init_with_mirror_fallback_repo(self) -> None:
138+
config = copy.copy(TEST_CONFIG)
139+
config["branches"][TEST_BRANCH]["mirror_fallback_repo"] = "linux.git"
140+
kpd_config = KPDConfig.from_json(config)
141+
gh = GithubSyncMock(kpd_config=kpd_config)
142+
self.assertEqual("linux.git", gh.workers[TEST_BRANCH].mirror_fallback_repo)
143+
130144
def test_close_existing_prs_for_series(self) -> None:
131145
matching_pr_mock = MagicMock()
132146
matching_pr_mock.title = "matching"

0 commit comments

Comments
 (0)