Skip to content

Commit 0e4bc4a

Browse files
authored
Merge pull request #212 from AlmaLinux/fix-qa
Copy cached QA repos on Nebula VMs if they exist
2 parents 90bcaa5 + 155b2af commit 0e4bc4a

6 files changed

Lines changed: 148 additions & 8 deletions

File tree

alts/shared/models.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,13 @@ class ThirdPartyRepoSshHost(BaseModel):
243243
user: Optional[str] = None
244244

245245

246+
class CachedTestRepo(BaseModel):
247+
# Baked-in repo cache (`src`) on the VM image, re-seeded into the
248+
# host-aware path (`dest`) during provisioning to avoid a full clone.
249+
src: str
250+
dest: str
251+
252+
246253
class CeleryConfig(BaseModel):
247254
def __init__(self, **data):
248255
super().__init__(**data)
@@ -353,6 +360,10 @@ def __init__(self, **data):
353360
# default (current SSH user). Configured per-deployment; empty by
354361
# default so no `~/.ssh/config` is written unless explicitly set.
355362
third_party_repo_ssh_hosts: List[ThirdPartyRepoSshHost] = []
363+
# Baked-in QA repo caches to re-seed into the host-aware layout during
364+
# provisioning (avoids a full network clone on every VM). Empty by
365+
# default; configured per-deployment to match the VM image's caches.
366+
cached_test_repos: List[CachedTestRepo] = []
356367
tests_base_dir: str = '/tests'
357368
package_proxy: str = ''
358369
disabled_al_repos: List[str] = []

alts/worker/runners/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,10 @@ def initial_provision(self, verbose=False):
751751
entry.model_dump(exclude_none=True)
752752
for entry in CONFIG.third_party_repo_ssh_hosts
753753
],
754+
'cached_test_repos': [
755+
entry.model_dump()
756+
for entry in CONFIG.cached_test_repos
757+
],
754758
}
755759
dist_major_version = self.dist_version[0]
756760
if self.dist_name in CONFIG.rhel_flavors and dist_major_version in ('6', '7'):

resources/roles/preparation/defaults/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ centos_repo_baseurl: "https://vault.centos.org"
66
# extra-var by the runner. Empty list here is a no-op fallback so the
77
# role still works if invoked standalone (e.g. ansible-lint).
88
third_party_repo_ssh_hosts: []
9+
# Source of truth lives in the alts config (`CelerConfig.cached_test_repos`)
10+
# and is passed in as an extra-var by the runner. Empty list here is a
11+
# no-op fallback so the role still works if invoked standalone.
12+
cached_test_repos: []

resources/roles/preparation/tasks/main.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,49 @@
6262
tags:
6363
- initial_provision
6464

65+
# Re-seed baked-in QA caches into the host-aware layout so the test
66+
# runner reuses them (light `git pull`) instead of a full network clone.
67+
# Docker envs receive repos via `docker cp` from the worker, so skip them.
68+
- name: Check which baked test repo caches exist
69+
ansible.builtin.stat:
70+
path: "{{ item.src }}"
71+
register: cached_test_repo_stats
72+
loop: "{{ cached_test_repos }}"
73+
when:
74+
- connection_type != 'docker'
75+
- cached_test_repos | length > 0
76+
tags:
77+
- initial_provision
78+
79+
- name: Ensure parent directories for seeded test repos exist
80+
ansible.builtin.file:
81+
path: "{{ item.item.dest | dirname }}"
82+
state: directory
83+
mode: '0755'
84+
loop: "{{ cached_test_repo_stats.results | default([]) }}"
85+
loop_control:
86+
label: "{{ item.item.dest }}"
87+
when:
88+
- not (item.skipped | default(false))
89+
- item.stat.exists
90+
tags:
91+
- initial_provision
92+
93+
- name: Seed host-aware test repo paths from baked caches
94+
ansible.builtin.copy:
95+
src: "{{ item.item.src }}/"
96+
dest: "{{ item.item.dest }}"
97+
remote_src: true
98+
mode: preserve
99+
loop: "{{ cached_test_repo_stats.results | default([]) }}"
100+
loop_control:
101+
label: "{{ item.item.dest }}"
102+
when:
103+
- not (item.skipped | default(false))
104+
- item.stat.exists
105+
tags:
106+
- initial_provision
107+
65108
- name: Copy tests to test environment
66109
copy:
67110
src: "{{ integrity_tests_dir }}"

tests/runners/test_provision_ssh_hosts.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from alts.worker import runners
1616
from alts.worker.runners.base import GenericVMRunner
17-
from alts.shared.models import ThirdPartyRepoSshHost
17+
from alts.shared.models import CachedTestRepo, ThirdPartyRepoSshHost
1818

1919

2020
def _make_runner(tmp_path):
@@ -97,3 +97,58 @@ def fake_run(cmd_args, timeout=None):
9797

9898
extra_vars = _extract_extra_vars(captured['args'])
9999
assert extra_vars['third_party_repo_ssh_hosts'] == []
100+
101+
102+
class TestInitialProvisionCachedTestRepos:
103+
def test_extra_vars_include_configured_caches(
104+
self, tmp_path, monkeypatch,
105+
):
106+
monkeypatch.setattr(
107+
runners.base.CONFIG,
108+
'cached_test_repos',
109+
[
110+
CachedTestRepo(
111+
src='/opt/QA',
112+
dest='/opt/gerrit.cloudlinux.com/QA',
113+
),
114+
],
115+
raising=False,
116+
)
117+
captured = {}
118+
119+
def fake_run(cmd_args, timeout=None):
120+
captured['args'] = cmd_args
121+
return 0, '', ''
122+
123+
runner = _make_runner(tmp_path)
124+
runner.run_ansible_command = fake_run
125+
126+
runner.initial_provision()
127+
128+
extra_vars = _extract_extra_vars(captured['args'])
129+
assert extra_vars['cached_test_repos'] == [
130+
{'src': '/opt/QA', 'dest': '/opt/gerrit.cloudlinux.com/QA'},
131+
]
132+
133+
def test_extra_vars_empty_when_unconfigured(
134+
self, tmp_path, monkeypatch,
135+
):
136+
monkeypatch.setattr(
137+
runners.base.CONFIG,
138+
'cached_test_repos',
139+
[],
140+
raising=False,
141+
)
142+
captured = {}
143+
144+
def fake_run(cmd_args, timeout=None):
145+
captured['args'] = cmd_args
146+
return 0, '', ''
147+
148+
runner = _make_runner(tmp_path)
149+
runner.run_ansible_command = fake_run
150+
151+
runner.initial_provision()
152+
153+
extra_vars = _extract_extra_vars(captured['args'])
154+
assert extra_vars['cached_test_repos'] == []

tests/shared/test_third_party_ssh_hosts.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
"""
88
import pytest
99

10-
from alts.shared.models import CeleryConfig, ThirdPartyRepoSshHost
10+
from alts.shared.models import (
11+
CachedTestRepo,
12+
CeleryConfig,
13+
ThirdPartyRepoSshHost,
14+
)
1115

1216

1317
class TestThirdPartyRepoSshHost:
@@ -35,12 +39,31 @@ def test_host_is_required(self):
3539
ThirdPartyRepoSshHost()
3640

3741

42+
class TestCachedTestRepo:
43+
def test_requires_src_and_dest(self):
44+
entry = CachedTestRepo(
45+
src='/opt/QA',
46+
dest='/opt/gerrit.cloudlinux.com/QA',
47+
)
48+
assert entry.model_dump() == {
49+
'src': '/opt/QA',
50+
'dest': '/opt/gerrit.cloudlinux.com/QA',
51+
}
52+
53+
@pytest.mark.parametrize('kwargs', [{}, {'src': '/opt/QA'}])
54+
def test_missing_field_raises(self, kwargs):
55+
with pytest.raises(Exception):
56+
CachedTestRepo(**kwargs)
57+
58+
3859
class TestCeleryConfigDefault:
39-
def test_third_party_repo_ssh_hosts_defaults_to_empty(self):
40-
# No built-in hosts: an unconfigured deployment writes no
41-
# ~/.ssh/config (the role task is gated on a non-empty list).
42-
config = CeleryConfig.__new__(CeleryConfig)
43-
field = CeleryConfig.model_fields['third_party_repo_ssh_hosts']
44-
# Pydantic stores the default factory / default value.
60+
@pytest.mark.parametrize(
61+
'field_name',
62+
['third_party_repo_ssh_hosts', 'cached_test_repos'],
63+
)
64+
def test_field_defaults_to_empty(self, field_name):
65+
# No built-in entries: an unconfigured deployment is a no-op
66+
# (the role tasks are gated on non-empty lists).
67+
field = CeleryConfig.model_fields[field_name]
4568
default = field.get_default(call_default_factory=True)
4669
assert default == []

0 commit comments

Comments
 (0)