Skip to content

Commit 6a38b81

Browse files
committed
feature: add PulpProject class for Pulp-based package repository support
Add a `PulpProject` subclass of `GitbuilderProject` so teuthology can use Pulp-hosted package repos when `config.use_artifacts` is `pulp`, alongside the existing `Gitbuilder` and `Shaman` artifact backends. Signed-off-by: Vaibhav Mahajan <vamahaja@redhat.com>
1 parent 426ec63 commit 6a38b81

File tree

8 files changed

+339
-17
lines changed

8 files changed

+339
-17
lines changed

docs/detailed_test_config.rst

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,24 @@ Shaman options
300300
==============
301301

302302
Shaman is a helper class which could be used to build the uri for specified
303-
packages based the 'shaman_host': 'shaman.ceph.com'.
303+
packages based the 'artifacts_host' option.
304304

305305
Options::
306306

307-
use_shaman: True # Enable to use Shaman, False as default
307+
use_artifacts: 'shaman'
308+
artifacts_host: 'shaman.ceph.com'
308309
shaman:
309310
force_noarch: True # Force to use "noarch" to build the uri
311+
312+
Pulp options
313+
============
314+
315+
Pulp is a package manager (https://pulpproject.org/) that could be used to
316+
build the uri for specified packages based the 'artifacts_host' option.
317+
318+
Options::
319+
320+
use_artifacts: 'pulp'
321+
artifacts_host: 'pulp.example.com'
322+
pulp:
323+
force_noarch: True # Force to use "noarch" to build the uri

docs/siteconfig.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,12 @@ Here is a sample configuration with many of the options set and documented::
269269
# Do not allow more than that many jobs in a single run by default.
270270
# To disable this check use 0.
271271
job_threshold: 500
272+
273+
# Setting for artifacts manager (shaman or pulp)
274+
use_artifacts: 'pulp'
275+
artifacts_host: 'pulp.example.com'
276+
277+
# Settings for pulp package manager (https://pulpproject.org/)
278+
pulp:
279+
username: '<username>'
280+
password: '<password>'

teuthology/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ class TeuthologyConfig(YamlConfig):
181181
'kojiroot_url': 'https://kojipkgs.fedoraproject.org/packages',
182182
'koji_task_url': 'https://kojipkgs.fedoraproject.org/work/',
183183
'baseurl_template': 'http://{host}/{proj}-{pkg_type}-{dist}-{arch}-{flavor}/{uri}',
184-
'use_shaman': True,
185-
'shaman_host': 'shaman.ceph.com',
184+
'use_artifacts': 'shaman',
185+
'artifacts_host': 'shaman.ceph.com',
186186
'teuthology_path': None,
187187
'suite_verify_ceph_hash': True,
188188
'suite_allow_missing_packages': False,

teuthology/openstack/setup-openstack.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function create_config() {
5454

5555
cat > ~/.teuthology.yaml <<EOF
5656
$archive_upload
57-
use_shaman: false
57+
use_artifacts: 'shaman'
5858
archive_upload_key: teuthology/openstack/archive-key
5959
lock_server: http://localhost:8080/
6060
results_server: http://localhost:8080/

teuthology/packaging.py

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ def _remove_deb_repo(self):
847847
class ShamanProject(GitbuilderProject):
848848
def __init__(self, project, job_config, ctx=None, remote=None):
849849
super(ShamanProject, self).__init__(project, job_config, ctx, remote)
850-
self.query_url = 'https://%s/api/' % config.shaman_host
850+
self.query_url = 'https://%s/api/' % config.artifacts_host
851851

852852
# Force to use the "noarch" instead to build the uri.
853853
self.force_noarch = self.job_config.get("shaman", {}).get("force_noarch", False)
@@ -1054,13 +1054,113 @@ def _remove_rpm_repo(self):
10541054
)
10551055

10561056

1057+
class PulpProject(GitbuilderProject):
1058+
def __init__(self, project, job_config, ctx=None, remote=None):
1059+
super(PulpProject, self).__init__(project, job_config, ctx, remote)
1060+
1061+
# Set the url for the pulp server.
1062+
self.pulp_server_url = f'http://{config.artifacts_host}'
1063+
self.pulp_username = config.pulp.get("username")
1064+
self.pulp_password = config.pulp.get("password")
1065+
1066+
if not self.pulp_username or not self.pulp_password:
1067+
raise ValueError("Pulp username and password are required")
1068+
1069+
# Force to use the "noarch" instead to build the uri.
1070+
self.force_noarch = self.job_config.get('pulp', {}).get('force_noarch', False)
1071+
1072+
@property
1073+
def _search_uri(self):
1074+
"""Build the search url"""
1075+
tail = 'rpm' if self.pkg_type == 'rpm' else 'apt'
1076+
path = f'pulp/api/v3/distributions/{self.pkg_type}/{tail}'
1077+
return urljoin(self.pulp_server_url, path)
1078+
1079+
@property
1080+
def _result(self):
1081+
"""Get the results from the pulp api"""
1082+
if getattr(self, '_result_obj', None) is None:
1083+
# Get the results from the pulp api.
1084+
self._result_obj = self._search().get('results', [])
1085+
1086+
# Check if there is exactly one result.
1087+
if not len(self._result_obj):
1088+
log.error(f'No results found for {self._search_uri}')
1089+
raise VersionNotFoundError(f'No results found for {self._search_uri}')
1090+
elif len(self._result_obj) > 1:
1091+
log.error(f'Multiple results found for {self._search_uri}')
1092+
raise VersionNotFoundError(f'Multiple results found for {self._search_uri}')
1093+
1094+
return self._result_obj[0]
1095+
1096+
@property
1097+
def repo_url(self):
1098+
"""Get the repo url from the pulp api"""
1099+
return urljoin(self.pulp_server_url, self._result.get('base_url', ''))
1100+
1101+
def _get_base_url(self):
1102+
"""Get the base url from the pulp api"""
1103+
return urljoin(
1104+
self.pulp_server_url,
1105+
"/".join(self._result.get('base_url', '').split('/')[:-2])
1106+
)
1107+
1108+
def _search(self):
1109+
"""Search for the package in the pulp api"""
1110+
# Build the search parameters.
1111+
labels = f'project:{self.project},'
1112+
labels += f'flavor:{self.flavor},'
1113+
labels += f'distro:{self.os_type},'
1114+
labels += f'distro_version:{self.os_version},'
1115+
1116+
# Add the architecture to the search parameters.
1117+
arch = 'noarch' if self.force_noarch else self.arch
1118+
labels += f'arch:{arch},'
1119+
1120+
# Add the reference to the search parameters.
1121+
ref_name, ref_val = list(self._choose_reference().items())[0]
1122+
labels += f'{ref_name}:{ref_val}'
1123+
1124+
resp = requests.get(
1125+
self._search_uri,
1126+
params={'pulp_label_select': labels},
1127+
auth=(self.pulp_username, self.pulp_password)
1128+
)
1129+
if not resp.ok:
1130+
log.error(f'Failed to get packages with labels: {labels}')
1131+
raise VersionNotFoundError(f'Failed to get packages with labels: {labels}')
1132+
1133+
return resp.json()
1134+
1135+
@classmethod
1136+
def _get_distro(cls, distro=None, version=None, codename=None):
1137+
if distro in ('centos', 'rhel'):
1138+
distro = 'centos'
1139+
version = cls._parse_version(version)
1140+
if distro in ('alma', 'rocky'):
1141+
version = cls._parse_version(version)
1142+
if distro in ('ubuntu', 'debian'):
1143+
version = codename or version
1144+
return f'{distro}/{version}'
1145+
1146+
def _get_package_sha1(self):
1147+
"""Get the package sha1 from the pulp api"""
1148+
return self._result.get('pulp_labels', {}).get('sha1', None)
1149+
1150+
def _get_package_version(self):
1151+
"""Get the package version from the pulp api"""
1152+
return self._result.get('pulp_labels', {}).get('version', None)
1153+
1154+
10571155
def get_builder_project():
10581156
"""
1059-
Depending on whether config.use_shaman is True or False, return
1060-
GitbuilderProject or ShamanProject (the class, not an instance).
1157+
Depending on whether config.use_artifacts is 'shaman' or 'pulp', return
1158+
GitbuilderProject, ShamanProject or PulpProject (the class, not an instance).
10611159
"""
1062-
if config.use_shaman is True:
1160+
if config.use_artifacts == 'shaman':
10631161
builder_class = ShamanProject
1162+
elif config.use_artifacts == 'pulp':
1163+
builder_class = PulpProject
10641164
else:
10651165
builder_class = GitbuilderProject
10661166
return builder_class

teuthology/run.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,10 @@ def main(args):
390390
# fetches the tasks and returns a new suite_path if needed
391391
config["suite_path"] = fetch_tasks_if_needed(config)
392392

393-
# If the job has a 'use_shaman' key, use that value to override the global
394-
# config's value.
395-
if config.get('use_shaman') is not None:
396-
teuth_config.use_shaman = config['use_shaman']
393+
# If the job has a 'use_artifacts' key, use that value to override the global
394+
# config's 'use_artifacts' value.
395+
if config.get('use_artifacts') is not None:
396+
teuth_config.use_artifacts = config['use_artifacts']
397397

398398
#could be refactored for setting and unsetting in hackish way
399399
if interactive_on_error:

teuthology/task/kernel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def download_kernel(ctx, config):
374374
ctx=ctx,
375375
remote=role_remote,
376376
)
377-
if teuth_config.use_shaman:
377+
if teuth_config.use_artifacts == 'shaman':
378378
if role_remote.os.package_type == 'rpm':
379379
arch = builder.arch
380380
baseurl = urljoin(
@@ -1442,7 +1442,7 @@ def process_role(ctx, config, timeout, role, role_config):
14421442
ctx.summary['{role}-kernel-sha1'.format(role=role)] = sha1
14431443

14441444
if need_to_install(ctx, role, sha1):
1445-
if teuth_config.use_shaman:
1445+
if teuth_config.use_artifacts == 'shaman':
14461446
version = builder.scm_version
14471447
else:
14481448
version = builder.version

0 commit comments

Comments
 (0)