Skip to content

Commit b2e5898

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 b2e5898

File tree

8 files changed

+142
-17
lines changed

8 files changed

+142
-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: 106 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,115 @@ 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+
return urljoin(
1076+
self.pulp_server_url,
1077+
f'pulp/api/v3/distributions/{self.pkg_type}/',
1078+
self.pkg_type if self.pkg_type == 'rpm' else 'apt'
1079+
)
1080+
1081+
@property
1082+
def _result(self):
1083+
"""Get the results from the pulp api"""
1084+
if getattr(self, '_result_obj', None) is None:
1085+
# Get the results from the pulp api.
1086+
self._result_obj = self._search().get('results', [])
1087+
1088+
# Check if there is exactly one result.
1089+
if not len(self._result_obj):
1090+
log.error(f'No results found for {self._search_uri}')
1091+
raise VersionNotFoundError(f'No results found for {self._search_uri}')
1092+
elif len(self._result_obj) > 1:
1093+
log.error(f'Multiple results found for {self._search_uri}')
1094+
raise VersionNotFoundError(f'Multiple results found for {self._search_uri}')
1095+
1096+
return self._result_obj[0]
1097+
1098+
@property
1099+
def repo_url(self):
1100+
"""Get the repo url from the pulp api"""
1101+
return urljoin(self.pulp_server_url, self._result.get('base_url', ''))
1102+
1103+
def _get_base_url(self):
1104+
"""Get the base url from the pulp api"""
1105+
return urljoin(
1106+
self.pulp_server_url,
1107+
"/".join(self._result.get('base_url', '').split('/')[:-2])
1108+
)
1109+
1110+
def _search(self):
1111+
"""Search for the package in the pulp api"""
1112+
# Build the search parameters.
1113+
labels = f'project:{self.project},'
1114+
labels += f'flavor:{self.flavor},'
1115+
labels += f'distro:{self.os_type},'
1116+
labels += f'distro_version:{self.os_version},'
1117+
1118+
# Add the architecture to the search parameters.
1119+
arch = 'noarch' if self.force_noarch else self.arch
1120+
labels += f'arch:{arch},'
1121+
1122+
# Add the reference to the search parameters.
1123+
ref_name, ref_val = list(self._choose_reference().items())[0]
1124+
labels += f'{ref_name}:{ref_val}'
1125+
1126+
resp = requests.get(
1127+
self._search_uri,
1128+
params={'pulp_label_select': labels},
1129+
auth=(self.pulp_username, self.pulp_password)
1130+
)
1131+
if not resp.ok:
1132+
log.error(f'Failed to get packages with labels: {labels}')
1133+
raise VersionNotFoundError(f'Failed to get packages with labels: {labels}')
1134+
1135+
return resp.json()
1136+
1137+
@classmethod
1138+
def _get_distro(cls, distro=None, version=None, codename=None):
1139+
if distro in ('centos', 'rhel'):
1140+
distro = 'centos'
1141+
version = cls._parse_version(version)
1142+
if distro in ('alma', 'rocky'):
1143+
version = cls._parse_version(version)
1144+
if distro in ('ubuntu', 'debian'):
1145+
version = codename
1146+
return f'{distro}/{version}'
1147+
1148+
def _get_package_sha1(self):
1149+
"""Get the package sha1 from the pulp api"""
1150+
return self._result.get('pulp_labels', {}).get('sha1', None)
1151+
1152+
def _get_package_version(self):
1153+
"""Get the package version from the pulp api"""
1154+
return self._result.get('pulp_labels', {}).get('version', None)
1155+
1156+
10571157
def get_builder_project():
10581158
"""
1059-
Depending on whether config.use_shaman is True or False, return
1060-
GitbuilderProject or ShamanProject (the class, not an instance).
1159+
Depending on whether config.use_artifacts is 'shaman' or 'pulp', return
1160+
GitbuilderProject, ShamanProject or PulpProject (the class, not an instance).
10611161
"""
1062-
if config.use_shaman is True:
1162+
if config.use_artifacts == 'shaman':
10631163
builder_class = ShamanProject
1164+
elif config.use_artifacts == 'pulp':
1165+
builder_class = PulpProject
10641166
else:
10651167
builder_class = GitbuilderProject
10661168
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

teuthology/test/test_packaging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,8 @@ class TestShamanProject(TestBuilderProject):
533533
def setup_method(self):
534534
self.p_config = patch('teuthology.packaging.config')
535535
self.m_config = self.p_config.start()
536-
self.m_config.use_shaman = True
537-
self.m_config.shaman_host = 'shaman.ceph.com'
536+
self.m_config.use_artifacts = 'shaman'
537+
self.m_config.artifacts_host = 'shaman.ceph.com'
538538
self.p_get_config_value = \
539539
patch('teuthology.packaging._get_config_value_for_remote')
540540
self.m_get_config_value = self.p_get_config_value.start()

0 commit comments

Comments
 (0)