Skip to content

Commit f5e6c7a

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 fa987f7 commit f5e6c7a

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
@@ -547,6 +547,8 @@ def __init__(
547547
app_auth: Optional[Auth.AppInstallationAuth] = None,
548548
email: Optional[EmailConfig] = None,
549549
http_retries: Optional[int] = None,
550+
mirror_fallback_repo: Optional[str] = None,
551+
mirror_dir: Optional[str] = None,
550552
) -> None:
551553
super().__init__(
552554
repo_url=repo_url,
@@ -559,6 +561,8 @@ def __init__(
559561
self.email = email
560562

561563
self.log_extractor = log_extractor
564+
self.mirror_dir = mirror_dir
565+
self.mirror_fallback_repo = mirror_fallback_repo
562566
self.ci_repo_url = ci_repo_url
563567
self.ci_repo_dir = _uniq_tmp_folder(ci_repo_url, ci_branch, base_directory)
564568
self.ci_branch = ci_branch
@@ -682,9 +686,27 @@ def do_sync(self) -> None:
682686
def full_sync(self, path: str, url: str, branch: str) -> git.Repo:
683687
logging.info(f"Doing full clone from {redact_url(url)}, branch: {branch}")
684688

689+
multi_opts: Optional[List[str]] = None
690+
if self.mirror_dir:
691+
upstream_name = os.path.basename(self.upstream_url)
692+
reference_path = os.path.join(self.mirror_dir, upstream_name)
693+
694+
# If primary mirror doesn't exist, try fallback path
695+
if not os.path.exists(reference_path) and self.mirror_fallback_repo:
696+
fallback_path = os.path.join(self.mirror_dir, self.mirror_fallback_repo)
697+
if os.path.exists(fallback_path):
698+
reference_path = fallback_path
699+
700+
# Use --reference-if-able when mirror path exists
701+
if os.path.exists(reference_path):
702+
multi_opts = ["--reference-if-able", reference_path]
703+
685704
with HistogramMetricTimer(git_clone_duration, {"branch": branch}):
686705
shutil.rmtree(path, ignore_errors=True)
687-
repo = git.Repo.clone_from(url, path)
706+
if multi_opts:
707+
repo = git.Repo.clone_from(url, path, multi_options=multi_opts)
708+
else:
709+
repo = git.Repo.clone_from(url, path)
688710
_reset_repo(repo, f"origin/{branch}")
689711

690712
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

@@ -171,6 +173,7 @@ class KPDConfig:
171173
branches: Dict[str, BranchConfig]
172174
tag_to_branch_mapping: Dict[str, List[str]]
173175
base_directory: str
176+
mirror_dir: Optional[str] = None
174177

175178
@classmethod
176179
def from_json(cls, json: Dict) -> "KPDConfig":
@@ -203,6 +206,7 @@ def from_json(cls, json: Dict) -> "KPDConfig":
203206
for name, json_config in json["branches"].items()
204207
},
205208
base_directory=json["base_directory"],
209+
mirror_dir=json.get("mirror_dir"),
206210
)
207211

208212
@classmethod

kernel_patches_daemon/github_sync.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def __init__(
114114
ci_branch=branch_config.ci_branch,
115115
log_extractor=_log_extractor_from_project(kpd_config.patchwork.project),
116116
base_directory=kpd_config.base_directory,
117+
mirror_dir=kpd_config.mirror_dir,
118+
mirror_fallback_repo=branch_config.mirror_fallback_repo,
117119
http_retries=http_retries,
118120
github_oauth_token=branch_config.github_oauth_token,
119121
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
@@ -68,6 +68,7 @@
6868
TEST_CI_REPO_URL = f"https://user:[email protected]/ci-org/{TEST_CI_REPO}"
6969
TEST_CI_BRANCH = "test_ci_branch"
7070
TEST_BASE_DIRECTORY = "/repos"
71+
TEST_MIRROR_DIRECTORY = "/mirror"
7172
TEST_BRANCH = "test-branch"
7273
TEST_CONFIG: Dict[str, Any] = {
7374
"version": 2,
@@ -124,6 +125,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
124125
"ci_branch": TEST_CI_BRANCH,
125126
"log_extractor": DefaultGithubLogExtractor(),
126127
"base_directory": TEST_BASE_DIRECTORY,
128+
"mirror_dir": None,
129+
"mirror_fallback_repo": None,
127130
}
128131
presets.update(kwargs)
129132

@@ -464,6 +467,69 @@ def test_fetch_repo_path_exists_git_exception(self) -> None:
464467
self._bw.fetch_repo(*fetch_params)
465468
fr.assert_called_once_with(*fetch_params)
466469

470+
def test_full_sync_with_mirror_dir(self) -> None:
471+
bw = BranchWorkerMock(mirror_dir=TEST_MIRROR_DIRECTORY)
472+
mirror_path = os.path.join(
473+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
474+
)
475+
with (
476+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
477+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
478+
):
479+
exists.side_effect = lambda p: p == mirror_path
480+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
481+
bw.full_sync("somepath", "giturl", "branch")
482+
self._git_repo_mock.clone_from.assert_called_once_with(
483+
"giturl",
484+
"somepath",
485+
multi_options=["--reference-if-able", mirror_path],
486+
)
487+
488+
def test_full_sync_with_mirror_fallback_repo(self) -> None:
489+
fallback_repo = "linux.git"
490+
bw = BranchWorkerMock(
491+
mirror_dir=TEST_MIRROR_DIRECTORY, mirror_fallback_repo=fallback_repo
492+
)
493+
primary_mirror_path = os.path.join(
494+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
495+
)
496+
fallback_mirror_path = os.path.join(TEST_MIRROR_DIRECTORY, fallback_repo)
497+
with (
498+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
499+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
500+
):
501+
# Primary mirror doesn't exist, but fallback does
502+
exists.side_effect = lambda p: p == fallback_mirror_path
503+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
504+
bw.full_sync("somepath", "giturl", "branch")
505+
# Should use fallback mirror path for reference
506+
self._git_repo_mock.clone_from.assert_called_once_with(
507+
"giturl",
508+
"somepath",
509+
multi_options=["--reference-if-able", fallback_mirror_path],
510+
)
511+
512+
def test_full_sync_without_mirror_fallback_repo(self) -> None:
513+
bw = BranchWorkerMock(
514+
mirror_dir=TEST_MIRROR_DIRECTORY, mirror_fallback_repo=None
515+
)
516+
mirror_path = os.path.join(
517+
TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL)
518+
)
519+
with (
520+
patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists,
521+
patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm,
522+
):
523+
# Mirror doesn't exist, and no fallback repo specified - should not use reference
524+
exists.side_effect = lambda p: False
525+
bw.upstream_url = TEST_UPSTREAM_REPO_URL
526+
bw.full_sync("somepath", "giturl", "branch")
527+
# Should clone without reference
528+
self._git_repo_mock.clone_from.assert_called_once_with(
529+
"giturl",
530+
"somepath",
531+
)
532+
467533
def test_expire_branches(self) -> None:
468534
"""Only the branch that matches pattern and is expired should be deleted"""
469535
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
@@ -205,8 +205,19 @@ def test_valid(self) -> None:
205205
ci_branch="ci_branch",
206206
github_app_auth=None,
207207
github_oauth_token="TEST_OAUTH_TOKEN",
208+
mirror_fallback_repo=None,
208209
),
209210
},
210211
base_directory="/repos",
212+
mirror_dir=None,
211213
)
212214
self.assertEqual(config, expected_config)
215+
216+
def test_branch_mirror_fallback_repo(self) -> None:
217+
kpd_config_json = read_fixture("fixtures/kpd_config.json")
218+
kpd_config_json["branches"]["oauth"]["mirror_fallback_repo"] = "linux.git"
219+
220+
with patch("builtins.open", mock_open(read_data="TEST_KEY_FILE_CONTENT")):
221+
config = KPDConfig.from_json(kpd_config_json)
222+
223+
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
@@ -126,6 +126,20 @@ class TestCase:
126126
gh.workers[TEST_BRANCH].ci_repo_dir.startswith(case.prefix),
127127
)
128128

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

0 commit comments

Comments
 (0)