teuthology 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scripts/describe.py +1 -0
- scripts/dispatcher.py +62 -0
- scripts/exporter.py +18 -0
- scripts/lock.py +1 -1
- scripts/node_cleanup.py +58 -0
- scripts/openstack.py +9 -9
- scripts/results.py +12 -11
- scripts/run.py +4 -0
- scripts/schedule.py +4 -0
- scripts/suite.py +61 -16
- scripts/supervisor.py +44 -0
- scripts/update_inventory.py +10 -4
- scripts/wait.py +31 -0
- teuthology/__init__.py +24 -21
- teuthology/beanstalk.py +4 -3
- teuthology/config.py +17 -6
- teuthology/contextutil.py +18 -14
- teuthology/describe_tests.py +25 -18
- teuthology/dispatcher/__init__.py +365 -0
- teuthology/dispatcher/supervisor.py +374 -0
- teuthology/exceptions.py +54 -0
- teuthology/exporter.py +347 -0
- teuthology/kill.py +76 -75
- teuthology/lock/cli.py +16 -7
- teuthology/lock/ops.py +276 -70
- teuthology/lock/query.py +61 -44
- teuthology/ls.py +9 -18
- teuthology/misc.py +152 -137
- teuthology/nuke/__init__.py +12 -351
- teuthology/openstack/__init__.py +4 -3
- teuthology/openstack/openstack-centos-7.0-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.1-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.2-user-data.txt +1 -1
- teuthology/openstack/openstack-debian-8.0-user-data.txt +1 -1
- teuthology/openstack/openstack-opensuse-42.1-user-data.txt +1 -1
- teuthology/openstack/openstack-teuthology.cron +0 -1
- teuthology/orchestra/cluster.py +51 -9
- teuthology/orchestra/connection.py +23 -16
- teuthology/orchestra/console.py +111 -50
- teuthology/orchestra/daemon/cephadmunit.py +23 -5
- teuthology/orchestra/daemon/state.py +10 -3
- teuthology/orchestra/daemon/systemd.py +10 -8
- teuthology/orchestra/opsys.py +32 -11
- teuthology/orchestra/remote.py +369 -152
- teuthology/orchestra/run.py +21 -12
- teuthology/packaging.py +54 -15
- teuthology/provision/__init__.py +30 -10
- teuthology/provision/cloud/openstack.py +12 -6
- teuthology/provision/cloud/util.py +1 -2
- teuthology/provision/downburst.py +83 -29
- teuthology/provision/fog.py +68 -20
- teuthology/provision/openstack.py +5 -4
- teuthology/provision/pelagos.py +13 -5
- teuthology/repo_utils.py +91 -44
- teuthology/report.py +57 -35
- teuthology/results.py +5 -3
- teuthology/run.py +21 -15
- teuthology/run_tasks.py +114 -40
- teuthology/schedule.py +4 -3
- teuthology/scrape.py +28 -22
- teuthology/suite/__init__.py +75 -46
- teuthology/suite/build_matrix.py +34 -24
- teuthology/suite/fragment-merge.lua +105 -0
- teuthology/suite/matrix.py +31 -2
- teuthology/suite/merge.py +175 -0
- teuthology/suite/placeholder.py +8 -8
- teuthology/suite/run.py +204 -102
- teuthology/suite/util.py +67 -211
- teuthology/task/__init__.py +1 -1
- teuthology/task/ansible.py +101 -31
- teuthology/task/buildpackages.py +2 -2
- teuthology/task/ceph_ansible.py +13 -6
- teuthology/task/cephmetrics.py +2 -1
- teuthology/task/clock.py +33 -14
- teuthology/task/exec.py +18 -0
- teuthology/task/hadoop.py +2 -2
- teuthology/task/install/__init__.py +51 -22
- teuthology/task/install/bin/adjust-ulimits +16 -0
- teuthology/task/install/bin/daemon-helper +114 -0
- teuthology/task/install/bin/stdin-killer +263 -0
- teuthology/task/install/deb.py +24 -4
- teuthology/task/install/redhat.py +36 -32
- teuthology/task/install/rpm.py +41 -14
- teuthology/task/install/util.py +48 -22
- teuthology/task/internal/__init__.py +69 -11
- teuthology/task/internal/edit_sudoers.sh +10 -0
- teuthology/task/internal/lock_machines.py +3 -133
- teuthology/task/internal/redhat.py +48 -28
- teuthology/task/internal/syslog.py +31 -8
- teuthology/task/kernel.py +155 -147
- teuthology/task/lockfile.py +1 -1
- teuthology/task/mpi.py +10 -10
- teuthology/task/pcp.py +1 -1
- teuthology/task/selinux.py +17 -8
- teuthology/task/ssh_keys.py +6 -6
- teuthology/task/tests/__init__.py +137 -77
- teuthology/task/tests/test_fetch_coredumps.py +116 -0
- teuthology/task/tests/test_run.py +4 -4
- teuthology/timer.py +3 -3
- teuthology/util/loggerfile.py +19 -0
- teuthology/util/scanner.py +159 -0
- teuthology/util/sentry.py +52 -0
- teuthology/util/time.py +52 -0
- teuthology-1.2.0.data/scripts/adjust-ulimits +16 -0
- teuthology-1.2.0.data/scripts/daemon-helper +114 -0
- teuthology-1.2.0.data/scripts/stdin-killer +263 -0
- teuthology-1.2.0.dist-info/METADATA +89 -0
- teuthology-1.2.0.dist-info/RECORD +174 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/WHEEL +1 -1
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/entry_points.txt +5 -2
- scripts/nuke.py +0 -45
- scripts/worker.py +0 -37
- teuthology/nuke/actions.py +0 -456
- teuthology/openstack/test/__init__.py +0 -0
- teuthology/openstack/test/openstack-integration.py +0 -286
- teuthology/openstack/test/test_config.py +0 -35
- teuthology/openstack/test/test_openstack.py +0 -1695
- teuthology/orchestra/test/__init__.py +0 -0
- teuthology/orchestra/test/integration/__init__.py +0 -0
- teuthology/orchestra/test/integration/test_integration.py +0 -94
- teuthology/orchestra/test/test_cluster.py +0 -240
- teuthology/orchestra/test/test_connection.py +0 -106
- teuthology/orchestra/test/test_console.py +0 -217
- teuthology/orchestra/test/test_opsys.py +0 -404
- teuthology/orchestra/test/test_remote.py +0 -185
- teuthology/orchestra/test/test_run.py +0 -286
- teuthology/orchestra/test/test_systemd.py +0 -54
- teuthology/orchestra/test/util.py +0 -12
- teuthology/sentry.py +0 -18
- teuthology/test/__init__.py +0 -0
- teuthology/test/fake_archive.py +0 -107
- teuthology/test/fake_fs.py +0 -92
- teuthology/test/integration/__init__.py +0 -0
- teuthology/test/integration/test_suite.py +0 -86
- teuthology/test/task/__init__.py +0 -205
- teuthology/test/task/test_ansible.py +0 -624
- teuthology/test/task/test_ceph_ansible.py +0 -176
- teuthology/test/task/test_console_log.py +0 -88
- teuthology/test/task/test_install.py +0 -337
- teuthology/test/task/test_internal.py +0 -57
- teuthology/test/task/test_kernel.py +0 -243
- teuthology/test/task/test_pcp.py +0 -379
- teuthology/test/task/test_selinux.py +0 -35
- teuthology/test/test_config.py +0 -189
- teuthology/test/test_contextutil.py +0 -68
- teuthology/test/test_describe_tests.py +0 -316
- teuthology/test/test_email_sleep_before_teardown.py +0 -81
- teuthology/test/test_exit.py +0 -97
- teuthology/test/test_get_distro.py +0 -47
- teuthology/test/test_get_distro_version.py +0 -47
- teuthology/test/test_get_multi_machine_types.py +0 -27
- teuthology/test/test_job_status.py +0 -60
- teuthology/test/test_ls.py +0 -48
- teuthology/test/test_misc.py +0 -368
- teuthology/test/test_nuke.py +0 -232
- teuthology/test/test_packaging.py +0 -763
- teuthology/test/test_parallel.py +0 -28
- teuthology/test/test_repo_utils.py +0 -204
- teuthology/test/test_report.py +0 -77
- teuthology/test/test_results.py +0 -155
- teuthology/test/test_run.py +0 -238
- teuthology/test/test_safepath.py +0 -55
- teuthology/test/test_schedule.py +0 -45
- teuthology/test/test_scrape.py +0 -167
- teuthology/test/test_timer.py +0 -80
- teuthology/test/test_vps_os_vers_parameter_checking.py +0 -84
- teuthology/test/test_worker.py +0 -303
- teuthology/worker.py +0 -339
- teuthology-1.0.0.dist-info/METADATA +0 -76
- teuthology-1.0.0.dist-info/RECORD +0 -210
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/LICENSE +0 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/top_level.txt +0 -0
teuthology/suite/placeholder.py
CHANGED
@@ -45,12 +45,13 @@ def substitute_placeholders(input_dict, values_dict):
|
|
45
45
|
# Template for the config that becomes the base for each generated job config
|
46
46
|
dict_templ = {
|
47
47
|
'branch': Placeholder('ceph_branch'),
|
48
|
+
'expire': Placeholder('expire'),
|
48
49
|
'sha1': Placeholder('ceph_hash'),
|
49
50
|
'teuthology_branch': Placeholder('teuthology_branch'),
|
51
|
+
'teuthology_sha1': Placeholder('teuthology_sha1'),
|
50
52
|
'archive_upload': Placeholder('archive_upload'),
|
51
53
|
'archive_upload_key': Placeholder('archive_upload_key'),
|
52
54
|
'machine_type': Placeholder('machine_type'),
|
53
|
-
'nuke-on-error': True,
|
54
55
|
'os_type': Placeholder('distro'),
|
55
56
|
'os_version': Placeholder('distro_version'),
|
56
57
|
'overrides': {
|
@@ -67,14 +68,13 @@ dict_templ = {
|
|
67
68
|
'debug mgr': 20,
|
68
69
|
'debug ms': 1},
|
69
70
|
'osd': {
|
70
|
-
'debug
|
71
|
-
'debug
|
72
|
-
'debug ms': 20,
|
73
|
-
'debug osd': 25
|
71
|
+
'debug ms': 1,
|
72
|
+
'debug osd': 20
|
74
73
|
}
|
75
74
|
},
|
76
|
-
'
|
77
|
-
|
75
|
+
'flavor': Placeholder('flavor'),
|
76
|
+
'log-ignorelist': [r'\(MDS_ALL_DOWN\)',
|
77
|
+
r'\(MDS_UP_LESS_THAN_MAX\)'],
|
78
78
|
'sha1': Placeholder('ceph_hash'),
|
79
79
|
},
|
80
80
|
'ceph-deploy': {
|
@@ -83,13 +83,13 @@ dict_templ = {
|
|
83
83
|
'log file': '/var/log/ceph/ceph-$name.$pid.log'
|
84
84
|
},
|
85
85
|
'mon': {
|
86
|
-
'osd default pool size': 2
|
87
86
|
}
|
88
87
|
}
|
89
88
|
},
|
90
89
|
'install': {
|
91
90
|
'ceph': {
|
92
91
|
'sha1': Placeholder('ceph_hash'),
|
92
|
+
'flavor': Placeholder('flavor'),
|
93
93
|
}
|
94
94
|
},
|
95
95
|
'workunit': {
|
teuthology/suite/run.py
CHANGED
@@ -1,27 +1,30 @@
|
|
1
1
|
import copy
|
2
|
+
import datetime
|
2
3
|
import logging
|
3
4
|
import os
|
4
5
|
import pwd
|
6
|
+
import yaml
|
5
7
|
import re
|
6
8
|
import time
|
7
|
-
import yaml
|
8
9
|
|
9
10
|
from humanfriendly import format_timespan
|
10
11
|
|
11
|
-
from datetime import datetime
|
12
12
|
from tempfile import NamedTemporaryFile
|
13
|
+
from teuthology import repo_utils
|
13
14
|
|
14
15
|
from teuthology.config import config, JobConfig
|
15
16
|
from teuthology.exceptions import (
|
16
|
-
BranchNotFoundError, CommitNotFoundError,
|
17
|
+
BranchMismatchError, BranchNotFoundError, CommitNotFoundError,
|
17
18
|
)
|
18
19
|
from teuthology.misc import deep_merge, get_results_url
|
19
20
|
from teuthology.orchestra.opsys import OS
|
20
21
|
from teuthology.repo_utils import build_git_url
|
21
22
|
|
22
23
|
from teuthology.suite import util
|
24
|
+
from teuthology.suite.merge import config_merge
|
23
25
|
from teuthology.suite.build_matrix import build_matrix
|
24
26
|
from teuthology.suite.placeholder import substitute_placeholders, dict_templ
|
27
|
+
from teuthology.util.time import parse_offset, parse_timestamp, TIMESTAMP_FMT
|
25
28
|
|
26
29
|
log = logging.getLogger(__name__)
|
27
30
|
|
@@ -31,8 +34,7 @@ class Run(object):
|
|
31
34
|
WAIT_PAUSE = 5 * 60
|
32
35
|
__slots__ = (
|
33
36
|
'args', 'name', 'base_config', 'suite_repo_path', 'base_yaml_paths',
|
34
|
-
'base_args', '
|
35
|
-
'timestamp', 'user',
|
37
|
+
'base_args', 'kernel_dict', 'config_input', 'timestamp', 'user', 'os',
|
36
38
|
)
|
37
39
|
|
38
40
|
def __init__(self, args):
|
@@ -42,7 +44,7 @@ class Run(object):
|
|
42
44
|
self.args = args
|
43
45
|
# We assume timestamp is a datetime.datetime object
|
44
46
|
self.timestamp = self.args.timestamp or \
|
45
|
-
datetime.now().strftime(
|
47
|
+
datetime.datetime.now().strftime(TIMESTAMP_FMT)
|
46
48
|
self.user = self.args.user or pwd.getpwuid(os.getuid()).pw_name
|
47
49
|
|
48
50
|
self.name = self.make_run_name()
|
@@ -53,8 +55,6 @@ class Run(object):
|
|
53
55
|
config.ceph_qa_suite_git_url = self.args.suite_repo
|
54
56
|
|
55
57
|
self.base_config = self.create_initial_config()
|
56
|
-
# caches package versions to minimize requests to gbs
|
57
|
-
self.package_versions = dict()
|
58
58
|
|
59
59
|
# Interpret any relative paths as being relative to ceph-qa-suite
|
60
60
|
# (absolute paths are unchanged by this)
|
@@ -74,7 +74,7 @@ class Run(object):
|
|
74
74
|
self.args.suite,
|
75
75
|
self.args.ceph_branch,
|
76
76
|
self.args.kernel_branch or '-',
|
77
|
-
self.args.
|
77
|
+
self.args.flavor, worker
|
78
78
|
]
|
79
79
|
).replace('/', ':')
|
80
80
|
|
@@ -87,6 +87,16 @@ class Run(object):
|
|
87
87
|
|
88
88
|
:returns: A JobConfig object
|
89
89
|
"""
|
90
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
91
|
+
expires = self.get_expiration()
|
92
|
+
if expires:
|
93
|
+
if now > expires:
|
94
|
+
util.schedule_fail(
|
95
|
+
f"Refusing to schedule because the expiration date is in the past: {self.args.expire}",
|
96
|
+
dry_run=self.args.dry_run,
|
97
|
+
)
|
98
|
+
|
99
|
+
self.os = self.choose_os()
|
90
100
|
self.kernel_dict = self.choose_kernel()
|
91
101
|
ceph_hash = self.choose_ceph_hash()
|
92
102
|
# We don't store ceph_version because we don't use it yet outside of
|
@@ -98,8 +108,8 @@ class Run(object):
|
|
98
108
|
self.suite_repo_path = self.args.suite_dir
|
99
109
|
else:
|
100
110
|
self.suite_repo_path = util.fetch_repos(
|
101
|
-
suite_branch, test_name=self.name)
|
102
|
-
teuthology_branch = self.choose_teuthology_branch()
|
111
|
+
suite_branch, test_name=self.name, dry_run=self.args.dry_run)
|
112
|
+
teuthology_branch, teuthology_sha1 = self.choose_teuthology_branch()
|
103
113
|
|
104
114
|
|
105
115
|
if self.args.distro_version:
|
@@ -113,16 +123,48 @@ class Run(object):
|
|
113
123
|
ceph_hash=ceph_hash,
|
114
124
|
ceph_repo=config.get_ceph_git_url(),
|
115
125
|
teuthology_branch=teuthology_branch,
|
126
|
+
teuthology_sha1=teuthology_sha1,
|
116
127
|
machine_type=self.args.machine_type,
|
117
|
-
distro=self.
|
118
|
-
distro_version=self.
|
128
|
+
distro=self.os.name,
|
129
|
+
distro_version=self.os.version,
|
119
130
|
archive_upload=config.archive_upload,
|
120
131
|
archive_upload_key=config.archive_upload_key,
|
121
132
|
suite_repo=config.get_ceph_qa_suite_git_url(),
|
122
133
|
suite_relpath=self.args.suite_relpath,
|
134
|
+
flavor=self.args.flavor,
|
135
|
+
expire=expires.strftime(TIMESTAMP_FMT) if expires else None,
|
123
136
|
)
|
124
137
|
return self.build_base_config()
|
125
138
|
|
139
|
+
def get_expiration(self, _base_time: datetime.datetime | None = None) -> datetime.datetime | None:
|
140
|
+
"""
|
141
|
+
_base_time: For testing, calculate relative offsets from this base time
|
142
|
+
|
143
|
+
:returns: True if the job should run; False if it has expired
|
144
|
+
"""
|
145
|
+
log.info(f"Checking for expiration ({self.args.expire})")
|
146
|
+
expires_str = self.args.expire
|
147
|
+
if expires_str is None:
|
148
|
+
return None
|
149
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
150
|
+
if _base_time is None:
|
151
|
+
_base_time = now
|
152
|
+
try:
|
153
|
+
expires = parse_timestamp(expires_str)
|
154
|
+
except ValueError:
|
155
|
+
expires = _base_time + parse_offset(expires_str)
|
156
|
+
return expires
|
157
|
+
|
158
|
+
def choose_os(self):
|
159
|
+
os_type = self.args.distro
|
160
|
+
os_version = self.args.distro_version
|
161
|
+
if not (os_type and os_version):
|
162
|
+
os_ = util.get_distro_defaults(
|
163
|
+
self.args.distro, self.args.machine_type)[2]
|
164
|
+
else:
|
165
|
+
os_ = OS(os_type, os_version)
|
166
|
+
return os_
|
167
|
+
|
126
168
|
def choose_kernel(self):
|
127
169
|
# Put together a stanza specifying the kernel hash
|
128
170
|
if self.args.kernel_branch == 'distro':
|
@@ -133,20 +175,25 @@ class Run(object):
|
|
133
175
|
kernel_hash = None
|
134
176
|
else:
|
135
177
|
kernel_hash = util.get_gitbuilder_hash(
|
136
|
-
'kernel', self.args.kernel_branch,
|
178
|
+
'kernel', self.args.kernel_branch, 'default',
|
137
179
|
self.args.machine_type, self.args.distro,
|
138
180
|
self.args.distro_version,
|
139
181
|
)
|
140
182
|
if not kernel_hash:
|
141
183
|
util.schedule_fail(
|
142
184
|
"Kernel branch '{branch}' not found".format(
|
143
|
-
branch=self.args.kernel_branch)
|
185
|
+
branch=self.args.kernel_branch),
|
186
|
+
dry_run=self.args.dry_run,
|
144
187
|
)
|
188
|
+
kdb = True
|
189
|
+
if self.args.kdb is not None:
|
190
|
+
kdb = self.args.kdb
|
191
|
+
|
145
192
|
if kernel_hash:
|
146
193
|
log.info("kernel sha1: {hash}".format(hash=kernel_hash))
|
147
|
-
kernel_dict = dict(kernel=dict(kdb=
|
194
|
+
kernel_dict = dict(kernel=dict(kdb=kdb, sha1=kernel_hash))
|
148
195
|
if kernel_hash != 'distro':
|
149
|
-
kernel_dict['kernel']['flavor'] =
|
196
|
+
kernel_dict['kernel']['flavor'] = 'default'
|
150
197
|
else:
|
151
198
|
kernel_dict = dict()
|
152
199
|
return kernel_dict
|
@@ -159,6 +206,7 @@ class Run(object):
|
|
159
206
|
"""
|
160
207
|
repo_name = self.ceph_repo_name
|
161
208
|
|
209
|
+
ceph_hash = None
|
162
210
|
if self.args.ceph_sha1:
|
163
211
|
ceph_hash = self.args.ceph_sha1
|
164
212
|
if self.args.validate_sha1:
|
@@ -168,17 +216,18 @@ class Run(object):
|
|
168
216
|
self.args.ceph_sha1,
|
169
217
|
'%s.git' % repo_name
|
170
218
|
)
|
171
|
-
util.schedule_fail(message=str(exc), name=self.name)
|
219
|
+
util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
|
172
220
|
log.info("ceph sha1 explicitly supplied")
|
173
221
|
|
174
222
|
elif self.args.ceph_branch:
|
175
|
-
ceph_hash = util.git_ls_remote(
|
223
|
+
ceph_hash = util.git_ls_remote(
|
224
|
+
self.args.ceph_repo, self.args.ceph_branch)
|
176
225
|
if not ceph_hash:
|
177
226
|
exc = BranchNotFoundError(
|
178
227
|
self.args.ceph_branch,
|
179
228
|
'%s.git' % repo_name
|
180
229
|
)
|
181
|
-
util.schedule_fail(message=str(exc), name=self.name)
|
230
|
+
util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
|
182
231
|
|
183
232
|
log.info("ceph sha1: {hash}".format(hash=ceph_hash))
|
184
233
|
return ceph_hash
|
@@ -187,21 +236,23 @@ class Run(object):
|
|
187
236
|
if config.suite_verify_ceph_hash and not self.args.newest:
|
188
237
|
# don't bother if newest; we'll search for an older one
|
189
238
|
# Get the ceph package version
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
239
|
+
ceph_version = util.package_version_for_hash(
|
240
|
+
ceph_hash, self.args.flavor, self.os.name,
|
241
|
+
self.os.version, self.args.machine_type,
|
242
|
+
)
|
243
|
+
if not ceph_version:
|
244
|
+
msg = f"Packages for os_type '{self.os.name}', flavor " \
|
245
|
+
f"{self.args.flavor} and ceph hash '{ceph_hash}' not found"
|
246
|
+
util.schedule_fail(msg, self.name, dry_run=self.args.dry_run)
|
197
247
|
log.info("ceph version: {ver}".format(ver=ceph_version))
|
198
248
|
return ceph_version
|
199
249
|
else:
|
200
250
|
log.info('skipping ceph package verification')
|
201
251
|
|
202
252
|
def choose_teuthology_branch(self):
|
203
|
-
"""Select teuthology branch, check if it is present in repo and
|
204
|
-
|
253
|
+
"""Select teuthology branch, check if it is present in repo and return
|
254
|
+
tuple (branch, hash) where hash is commit sha1 corresponding
|
255
|
+
to the HEAD of the branch.
|
205
256
|
|
206
257
|
The branch name value is determined in the following order:
|
207
258
|
|
@@ -217,9 +268,10 @@ class Run(object):
|
|
217
268
|
of the teuthology config files ``$HOME/teuthology.yaml``
|
218
269
|
or ``/etc/teuthology.yaml`` correspondingly.
|
219
270
|
|
220
|
-
Use ``
|
271
|
+
Use ``main``.
|
221
272
|
|
222
273
|
Generate exception if the branch is not present in the repo.
|
274
|
+
|
223
275
|
"""
|
224
276
|
teuthology_branch = self.args.teuthology_branch
|
225
277
|
if not teuthology_branch:
|
@@ -240,17 +292,34 @@ class Run(object):
|
|
240
292
|
log.warning(
|
241
293
|
'The teuthology branch config is empty, skipping')
|
242
294
|
if not teuthology_branch:
|
243
|
-
teuthology_branch = config.get('teuthology_branch'
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
teuthology_branch
|
248
|
-
|
249
|
-
|
295
|
+
teuthology_branch = config.get('teuthology_branch')
|
296
|
+
|
297
|
+
if config.teuthology_path:
|
298
|
+
actual_branch = repo_utils.current_branch(config.teuthology_path)
|
299
|
+
if teuthology_branch and actual_branch != teuthology_branch:
|
300
|
+
raise BranchMismatchError(
|
301
|
+
teuthology_branch,
|
302
|
+
config.teuthology_path,
|
303
|
+
"config.teuthology_path is set",
|
304
|
+
)
|
305
|
+
if not teuthology_branch:
|
306
|
+
teuthology_branch = actual_branch
|
307
|
+
teuthology_sha1 = util.git_ls_remote(
|
308
|
+
f"file://{config.teuthology_path}",
|
309
|
+
teuthology_branch
|
310
|
+
)
|
311
|
+
else:
|
312
|
+
if not teuthology_branch:
|
313
|
+
teuthology_branch = 'main'
|
314
|
+
teuthology_sha1 = util.git_ls_remote(
|
315
|
+
'teuthology',
|
316
|
+
teuthology_branch
|
317
|
+
)
|
318
|
+
if not teuthology_sha1:
|
250
319
|
exc = BranchNotFoundError(teuthology_branch, build_git_url('teuthology'))
|
251
|
-
util.schedule_fail(message=str(exc), name=self.name)
|
252
|
-
log.info("teuthology branch: %s %s", teuthology_branch,
|
253
|
-
return teuthology_branch
|
320
|
+
util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
|
321
|
+
log.info("teuthology branch: %s %s", teuthology_branch, teuthology_sha1)
|
322
|
+
return teuthology_branch, teuthology_sha1
|
254
323
|
|
255
324
|
@property
|
256
325
|
def ceph_repo_name(self):
|
@@ -268,32 +337,32 @@ class Run(object):
|
|
268
337
|
|
269
338
|
@staticmethod
|
270
339
|
def _repo_name(url):
|
271
|
-
return re.sub('\.git$', '', url.split('/')[-1])
|
340
|
+
return re.sub(r'\.git$', '', url.split('/')[-1])
|
272
341
|
|
273
342
|
def choose_suite_branch(self):
|
274
343
|
suite_repo_name = self.suite_repo_name
|
275
344
|
suite_repo_project_or_url = self.args.suite_repo or 'ceph-qa-suite'
|
276
345
|
suite_branch = self.args.suite_branch
|
277
346
|
ceph_branch = self.args.ceph_branch
|
278
|
-
if suite_branch and suite_branch != '
|
347
|
+
if suite_branch and suite_branch != 'main':
|
279
348
|
if not util.git_branch_exists(
|
280
349
|
suite_repo_project_or_url,
|
281
350
|
suite_branch
|
282
351
|
):
|
283
352
|
exc = BranchNotFoundError(suite_branch, suite_repo_name)
|
284
|
-
util.schedule_fail(message=str(exc), name=self.name)
|
353
|
+
util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
|
285
354
|
elif not suite_branch:
|
286
355
|
# Decide what branch of the suite repo to use
|
287
356
|
if util.git_branch_exists(suite_repo_project_or_url, ceph_branch):
|
288
357
|
suite_branch = ceph_branch
|
289
358
|
else:
|
290
359
|
log.info(
|
291
|
-
"branch {0} not in {1}; will use
|
360
|
+
"branch {0} not in {1}; will use main for"
|
292
361
|
" ceph-qa-suite".format(
|
293
362
|
ceph_branch,
|
294
363
|
suite_repo_name
|
295
364
|
))
|
296
|
-
suite_branch = '
|
365
|
+
suite_branch = 'main'
|
297
366
|
return suite_branch
|
298
367
|
|
299
368
|
def choose_suite_hash(self, suite_branch):
|
@@ -305,7 +374,7 @@ class Run(object):
|
|
305
374
|
)
|
306
375
|
if not suite_hash:
|
307
376
|
exc = BranchNotFoundError(suite_branch, suite_repo_name)
|
308
|
-
util.schedule_fail(message=str(exc), name=self.name)
|
377
|
+
util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
|
309
378
|
log.info("%s branch: %s %s", suite_repo_name, suite_branch, suite_hash)
|
310
379
|
return suite_hash
|
311
380
|
|
@@ -317,12 +386,17 @@ class Run(object):
|
|
317
386
|
job_config.user = self.user
|
318
387
|
job_config.timestamp = self.timestamp
|
319
388
|
job_config.priority = self.args.priority
|
389
|
+
job_config.seed = self.args.seed
|
390
|
+
if self.args.subset:
|
391
|
+
job_config.subset = '/'.join(str(i) for i in self.args.subset)
|
320
392
|
if self.args.email:
|
321
393
|
job_config.email = self.args.email
|
322
394
|
if self.args.owner:
|
323
395
|
job_config.owner = self.args.owner
|
324
396
|
if self.args.sleep_before_teardown:
|
325
397
|
job_config.sleep_before_teardown = int(self.args.sleep_before_teardown)
|
398
|
+
if self.args.rocketchat:
|
399
|
+
job_config.rocketchat = self.args.rocketchat
|
326
400
|
return job_config
|
327
401
|
|
328
402
|
def build_base_args(self):
|
@@ -349,6 +423,8 @@ class Run(object):
|
|
349
423
|
if self.args.subset:
|
350
424
|
subset = '/'.join(str(i) for i in self.args.subset)
|
351
425
|
args.extend(['--subset', subset])
|
426
|
+
if self.args.no_nested_subset:
|
427
|
+
args.extend(['--no-nested-subset'])
|
352
428
|
args.extend(['--seed', str(self.args.seed)])
|
353
429
|
util.teuthology_schedule(
|
354
430
|
args=args,
|
@@ -397,16 +473,13 @@ class Run(object):
|
|
397
473
|
def collect_jobs(self, arch, configs, newest=False, limit=0):
|
398
474
|
jobs_to_schedule = []
|
399
475
|
jobs_missing_packages = []
|
400
|
-
for description, fragment_paths in configs:
|
476
|
+
for description, fragment_paths, parsed_yaml in configs:
|
401
477
|
if limit > 0 and len(jobs_to_schedule) >= limit:
|
402
478
|
log.info(
|
403
479
|
'Stopped after {limit} jobs due to --limit={limit}'.format(
|
404
480
|
limit=limit))
|
405
481
|
break
|
406
482
|
|
407
|
-
raw_yaml = '\n'.join([open(a, 'r').read() for a in fragment_paths])
|
408
|
-
|
409
|
-
parsed_yaml = yaml.safe_load(raw_yaml)
|
410
483
|
os_type = parsed_yaml.get('os_type') or self.base_config.os_type
|
411
484
|
os_version = parsed_yaml.get('os_version') or self.base_config.os_version
|
412
485
|
exclude_arch = parsed_yaml.get('exclude_arch')
|
@@ -428,13 +501,16 @@ class Run(object):
|
|
428
501
|
'--',
|
429
502
|
])
|
430
503
|
arg.extend(self.base_yaml_paths)
|
431
|
-
|
504
|
+
|
505
|
+
parsed_yaml_txt = yaml.dump(parsed_yaml)
|
506
|
+
arg.append('-')
|
432
507
|
|
433
508
|
job = dict(
|
434
509
|
yaml=parsed_yaml,
|
435
510
|
desc=description,
|
436
511
|
sha1=self.base_config.sha1,
|
437
|
-
args=arg
|
512
|
+
args=arg,
|
513
|
+
stdin=parsed_yaml_txt,
|
438
514
|
)
|
439
515
|
|
440
516
|
sha1 = self.base_config.sha1
|
@@ -443,31 +519,16 @@ class Run(object):
|
|
443
519
|
full_job_config = copy.deepcopy(self.base_config.to_dict())
|
444
520
|
deep_merge(full_job_config, parsed_yaml)
|
445
521
|
flavor = util.get_install_task_flavor(full_job_config)
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
# again for them.
|
450
|
-
try:
|
451
|
-
self.package_versions = util.get_package_versions(
|
452
|
-
sha1,
|
453
|
-
os_type,
|
454
|
-
os_version,
|
455
|
-
flavor,
|
456
|
-
self.package_versions
|
457
|
-
)
|
458
|
-
except VersionNotFoundError:
|
459
|
-
pass
|
460
|
-
if not util.has_packages_for_distro(
|
461
|
-
sha1, os_type, os_version, flavor, self.package_versions
|
462
|
-
):
|
463
|
-
m = "Packages for os_type '{os}', flavor {flavor} and " + \
|
464
|
-
"ceph hash '{ver}' not found"
|
465
|
-
log.error(m.format(os=os_type, flavor=flavor, ver=sha1))
|
522
|
+
version = util.package_version_for_hash(sha1, flavor, os_type,
|
523
|
+
os_version, self.args.machine_type)
|
524
|
+
if not version:
|
466
525
|
jobs_missing_packages.append(job)
|
526
|
+
log.error(f"Packages for os_type '{os_type}', flavor {flavor} and "
|
527
|
+
f"ceph hash '{sha1}' not found")
|
467
528
|
# optimization: one missing package causes backtrack in newest mode;
|
468
529
|
# no point in continuing the search
|
469
530
|
if newest:
|
470
|
-
return jobs_missing_packages,
|
531
|
+
return jobs_missing_packages, []
|
471
532
|
|
472
533
|
jobs_to_schedule.append(job)
|
473
534
|
return jobs_missing_packages, jobs_to_schedule
|
@@ -481,26 +542,54 @@ class Run(object):
|
|
481
542
|
log_prefix = ''
|
482
543
|
if job in jobs_missing_packages:
|
483
544
|
log_prefix = "Missing Packages: "
|
484
|
-
if
|
485
|
-
not self.args.dry_run and
|
486
|
-
not config.suite_allow_missing_packages
|
487
|
-
):
|
545
|
+
if not config.suite_allow_missing_packages:
|
488
546
|
util.schedule_fail(
|
489
|
-
"At least one job needs packages that don't exist
|
490
|
-
"hash {
|
547
|
+
"At least one job needs packages that don't exist "
|
548
|
+
f"for hash {self.base_config.sha1}.",
|
491
549
|
name,
|
550
|
+
dry_run=self.args.dry_run,
|
492
551
|
)
|
493
552
|
util.teuthology_schedule(
|
494
553
|
args=job['args'],
|
495
554
|
dry_run=self.args.dry_run,
|
496
555
|
verbose=self.args.verbose,
|
497
556
|
log_prefix=log_prefix,
|
557
|
+
stdin=job['stdin'],
|
498
558
|
)
|
499
559
|
throttle = self.args.throttle
|
500
560
|
if not self.args.dry_run and throttle:
|
501
561
|
log.info("pause between jobs : --throttle " + str(throttle))
|
502
562
|
time.sleep(int(throttle))
|
503
563
|
|
564
|
+
def check_priority(self, jobs_to_schedule):
|
565
|
+
priority = self.args.priority
|
566
|
+
msg=f'''Unable to schedule {jobs_to_schedule} jobs with priority {priority}.
|
567
|
+
|
568
|
+
Use the following testing priority
|
569
|
+
10 to 49: Tests which are urgent and blocking other important development.
|
570
|
+
50 to 74: Testing a particular feature/fix with less than 25 jobs and can also be used for urgent release testing.
|
571
|
+
75 to 99: Tech Leads usually schedule integration tests with this priority to verify pull requests against main.
|
572
|
+
100 to 149: QE validation of point releases.
|
573
|
+
150 to 199: Testing a particular feature/fix with less than 100 jobs and results will be available in a day or so.
|
574
|
+
200 to 1000: Large test runs that can be done over the course of a week.
|
575
|
+
Note: To force run, use --force-priority'''
|
576
|
+
if priority < 50:
|
577
|
+
util.schedule_fail(msg, dry_run=self.args.dry_run)
|
578
|
+
elif priority < 75 and jobs_to_schedule > 25:
|
579
|
+
util.schedule_fail(msg, dry_run=self.args.dry_run)
|
580
|
+
elif priority < 150 and jobs_to_schedule > 100:
|
581
|
+
util.schedule_fail(msg, dry_run=self.args.dry_run)
|
582
|
+
|
583
|
+
def check_num_jobs(self, jobs_to_schedule):
|
584
|
+
"""
|
585
|
+
Fail schedule if number of jobs exceeds job threshold.
|
586
|
+
"""
|
587
|
+
threshold = self.args.job_threshold
|
588
|
+
msg=f'''Unable to schedule {jobs_to_schedule} jobs, too many jobs, when maximum {threshold} jobs allowed.
|
589
|
+
|
590
|
+
Note: If you still want to go ahead, use --job-threshold 0'''
|
591
|
+
if threshold and jobs_to_schedule > threshold:
|
592
|
+
util.schedule_fail(msg, dry_run=self.args.dry_run)
|
504
593
|
|
505
594
|
def schedule_suite(self):
|
506
595
|
"""
|
@@ -520,11 +609,21 @@ class Run(object):
|
|
520
609
|
self.base_config.suite.replace(':', '/'),
|
521
610
|
))
|
522
611
|
log.debug('Suite %s in %s' % (suite_name, suite_path))
|
612
|
+
log.debug(f"subset = {self.args.subset}")
|
613
|
+
log.debug(f"no_nested_subset = {self.args.no_nested_subset}")
|
523
614
|
configs = build_matrix(suite_path,
|
524
615
|
subset=self.args.subset,
|
616
|
+
no_nested_subset=self.args.no_nested_subset,
|
525
617
|
seed=self.args.seed)
|
526
|
-
|
527
|
-
|
618
|
+
generated = len(configs)
|
619
|
+
log.info(f'Suite {suite_name} in {suite_path} generated {generated} jobs (not yet filtered or merged)')
|
620
|
+
configs = list(config_merge(configs,
|
621
|
+
filter_in=self.args.filter_in,
|
622
|
+
filter_out=self.args.filter_out,
|
623
|
+
filter_all=self.args.filter_all,
|
624
|
+
filter_fragments=self.args.filter_fragments,
|
625
|
+
seed=self.args.seed,
|
626
|
+
suite_name=suite_name))
|
528
627
|
|
529
628
|
if self.args.dry_run:
|
530
629
|
log.debug("Base job config:\n%s" % self.base_config)
|
@@ -561,7 +660,7 @@ class Run(object):
|
|
561
660
|
'this run for {that_long}? (y/N):'
|
562
661
|
.format(
|
563
662
|
that_long=format_timespan(sleep_before_teardown),
|
564
|
-
total=
|
663
|
+
total=generated,
|
565
664
|
maximum=job_limit))
|
566
665
|
while True:
|
567
666
|
insane=(input(are_you_insane) or 'n').lower()
|
@@ -574,23 +673,18 @@ class Run(object):
|
|
574
673
|
# if not, do it once
|
575
674
|
backtrack = 0
|
576
675
|
limit = self.args.newest
|
676
|
+
sha1s = []
|
677
|
+
jobs_to_schedule = []
|
678
|
+
jobs_missing_packages = []
|
577
679
|
while backtrack <= limit:
|
578
680
|
jobs_missing_packages, jobs_to_schedule = \
|
579
|
-
self.collect_jobs(arch,
|
580
|
-
util.filter_configs(configs,
|
581
|
-
filter_in=self.args.filter_in,
|
582
|
-
filter_out=self.args.filter_out,
|
583
|
-
filter_all=self.args.filter_all,
|
584
|
-
filter_fragments=self.args.filter_fragments,
|
585
|
-
suite_name=suite_name),
|
586
|
-
self.args.newest, job_limit)
|
681
|
+
self.collect_jobs(arch, configs, self.args.newest, job_limit)
|
587
682
|
if jobs_missing_packages and self.args.newest:
|
588
|
-
|
589
|
-
util.
|
590
|
-
if
|
591
|
-
util.schedule_fail('Backtrack for --newest failed', name)
|
592
|
-
|
593
|
-
self.config_input['ceph_hash'] = new_sha1
|
683
|
+
if not sha1s:
|
684
|
+
sha1s = util.find_git_parents('ceph', str(self.base_config.sha1), self.args.newest)
|
685
|
+
if not sha1s:
|
686
|
+
util.schedule_fail('Backtrack for --newest failed', name, dry_run=self.args.dry_run)
|
687
|
+
self.config_input['ceph_hash'] = sha1s.pop(0)
|
594
688
|
self.base_config = self.build_base_config()
|
595
689
|
backtrack += 1
|
596
690
|
continue
|
@@ -603,31 +697,39 @@ class Run(object):
|
|
603
697
|
util.schedule_fail(
|
604
698
|
'Exceeded %d backtracks; raise --newest value' % limit,
|
605
699
|
name,
|
700
|
+
dry_run=self.args.dry_run,
|
606
701
|
)
|
607
702
|
|
608
|
-
if self.args.dry_run:
|
609
|
-
log.debug("Base job config:\n%s" % self.base_config)
|
610
|
-
|
611
703
|
with open(base_yaml_path, 'w+b') as base_yaml:
|
612
704
|
base_yaml.write(str(self.base_config).encode())
|
613
705
|
|
614
706
|
if jobs_to_schedule:
|
615
707
|
self.write_rerun_memo()
|
616
708
|
|
709
|
+
# Before scheduling jobs, check the priority
|
710
|
+
if self.args.priority and jobs_to_schedule and not self.args.force_priority:
|
711
|
+
self.check_priority(len(jobs_to_schedule))
|
712
|
+
|
713
|
+
self.check_num_jobs(len(jobs_to_schedule))
|
714
|
+
|
617
715
|
self.schedule_jobs(jobs_missing_packages, jobs_to_schedule, name)
|
618
716
|
|
619
717
|
os.remove(base_yaml_path)
|
620
718
|
|
621
719
|
count = len(jobs_to_schedule)
|
622
720
|
missing_count = len(jobs_missing_packages)
|
721
|
+
total_count = count
|
722
|
+
if self.args.num:
|
723
|
+
total_count *= self.args.num
|
623
724
|
log.info(
|
624
725
|
'Suite %s in %s scheduled %d jobs.' %
|
625
726
|
(suite_name, suite_path, count)
|
626
727
|
)
|
627
728
|
log.info('%d/%d jobs were filtered out.',
|
628
|
-
(
|
629
|
-
|
729
|
+
(generated - count),
|
730
|
+
generated)
|
630
731
|
if missing_count:
|
631
|
-
log.
|
732
|
+
log.warning('Scheduled %d/%d jobs that are missing packages!',
|
632
733
|
missing_count, count)
|
734
|
+
log.info('Scheduled %d jobs in total.', total_count)
|
633
735
|
return count
|