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
@@ -0,0 +1,374 @@
|
|
1
|
+
import datetime
|
2
|
+
import logging
|
3
|
+
import os
|
4
|
+
import subprocess
|
5
|
+
import time
|
6
|
+
import yaml
|
7
|
+
import requests
|
8
|
+
|
9
|
+
from urllib.parse import urljoin
|
10
|
+
|
11
|
+
from teuthology import exporter, dispatcher, kill, report, safepath
|
12
|
+
from teuthology.config import config as teuth_config
|
13
|
+
from teuthology.exceptions import SkipJob, MaxWhileTries
|
14
|
+
from teuthology import setup_log_file, install_except_hook
|
15
|
+
from teuthology.misc import get_user, archive_logs, compress_logs
|
16
|
+
from teuthology.config import FakeNamespace
|
17
|
+
from teuthology.lock import ops as lock_ops
|
18
|
+
from teuthology.task import internal
|
19
|
+
from teuthology.misc import decanonicalize_hostname as shortname
|
20
|
+
from teuthology.lock import query
|
21
|
+
from teuthology.util import sentry
|
22
|
+
|
23
|
+
log = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
def main(args):
|
27
|
+
with open(args.job_config, 'r') as config_file:
|
28
|
+
job_config = yaml.safe_load(config_file)
|
29
|
+
|
30
|
+
loglevel = logging.INFO
|
31
|
+
if args.verbose:
|
32
|
+
loglevel = logging.DEBUG
|
33
|
+
logging.getLogger().setLevel(loglevel)
|
34
|
+
log.setLevel(loglevel)
|
35
|
+
|
36
|
+
log_file_path = os.path.join(job_config['archive_path'],
|
37
|
+
f"supervisor.{job_config['job_id']}.log")
|
38
|
+
setup_log_file(log_file_path)
|
39
|
+
install_except_hook()
|
40
|
+
try:
|
41
|
+
dispatcher.check_job_expiration(job_config)
|
42
|
+
except SkipJob:
|
43
|
+
return 0
|
44
|
+
|
45
|
+
# reimage target machines before running the job
|
46
|
+
if 'targets' in job_config:
|
47
|
+
node_count = len(job_config["targets"])
|
48
|
+
# If a job (e.g. from the nop suite) doesn't need nodes, avoid
|
49
|
+
# submitting a zero here.
|
50
|
+
if node_count:
|
51
|
+
with exporter.NodeReimagingTime().time(
|
52
|
+
machine_type=job_config["machine_type"],
|
53
|
+
node_count=node_count,
|
54
|
+
):
|
55
|
+
reimage(job_config)
|
56
|
+
else:
|
57
|
+
reimage(job_config)
|
58
|
+
with open(args.job_config, 'w') as f:
|
59
|
+
yaml.safe_dump(job_config, f, default_flow_style=False)
|
60
|
+
|
61
|
+
suite = job_config.get("suite")
|
62
|
+
if suite:
|
63
|
+
with exporter.JobTime().time(suite=suite):
|
64
|
+
return run_job(
|
65
|
+
job_config,
|
66
|
+
args.bin_path,
|
67
|
+
args.archive_dir,
|
68
|
+
args.verbose
|
69
|
+
)
|
70
|
+
else:
|
71
|
+
return run_job(
|
72
|
+
job_config,
|
73
|
+
args.bin_path,
|
74
|
+
args.archive_dir,
|
75
|
+
args.verbose
|
76
|
+
)
|
77
|
+
|
78
|
+
|
79
|
+
def run_job(job_config, teuth_bin_path, archive_dir, verbose):
|
80
|
+
safe_archive = safepath.munge(job_config['name'])
|
81
|
+
if job_config.get('first_in_suite') or job_config.get('last_in_suite'):
|
82
|
+
job_archive = os.path.join(archive_dir, safe_archive)
|
83
|
+
args = [
|
84
|
+
os.path.join(teuth_bin_path, 'teuthology-results'),
|
85
|
+
'--archive-dir', job_archive,
|
86
|
+
'--name', job_config['name'],
|
87
|
+
]
|
88
|
+
if job_config.get('first_in_suite'):
|
89
|
+
log.info('Generating memo for %s', job_config['name'])
|
90
|
+
if job_config.get('seed'):
|
91
|
+
args.extend(['--seed', job_config['seed']])
|
92
|
+
if job_config.get('subset'):
|
93
|
+
args.extend(['--subset', job_config['subset']])
|
94
|
+
if job_config.get('no_nested_subset'):
|
95
|
+
args.extend(['--no-nested-subset'])
|
96
|
+
else:
|
97
|
+
log.info('Generating results for %s', job_config['name'])
|
98
|
+
timeout = job_config.get('results_timeout',
|
99
|
+
teuth_config.results_timeout)
|
100
|
+
args.extend(['--timeout', str(timeout)])
|
101
|
+
if job_config.get('email'):
|
102
|
+
args.extend(['--email', job_config['email']])
|
103
|
+
# Execute teuthology-results, passing 'preexec_fn=os.setpgrp' to
|
104
|
+
# make sure that it will continue to run if this worker process
|
105
|
+
# dies (e.g. because of a restart)
|
106
|
+
result_proc = subprocess.Popen(args=args, preexec_fn=os.setpgrp)
|
107
|
+
log.info("teuthology-results PID: %s", result_proc.pid)
|
108
|
+
# Remove unnecessary logs for first and last jobs in run
|
109
|
+
log.info('Deleting job\'s archive dir %s', job_config['archive_path'])
|
110
|
+
for f in os.listdir(job_config['archive_path']):
|
111
|
+
os.remove(os.path.join(job_config['archive_path'], f))
|
112
|
+
os.rmdir(job_config['archive_path'])
|
113
|
+
return
|
114
|
+
|
115
|
+
log.info('Running job %s', job_config['job_id'])
|
116
|
+
|
117
|
+
arg = [
|
118
|
+
os.path.join(teuth_bin_path, 'teuthology'),
|
119
|
+
]
|
120
|
+
# The following is for compatibility with older schedulers, from before we
|
121
|
+
# started merging the contents of job_config['config'] into job_config
|
122
|
+
# itself.
|
123
|
+
if 'config' in job_config:
|
124
|
+
inner_config = job_config.pop('config')
|
125
|
+
if not isinstance(inner_config, dict):
|
126
|
+
log.warning("run_job: job_config['config'] isn't a dict, it's a %s",
|
127
|
+
str(type(inner_config)))
|
128
|
+
else:
|
129
|
+
job_config.update(inner_config)
|
130
|
+
|
131
|
+
if verbose or job_config['verbose']:
|
132
|
+
arg.append('-v')
|
133
|
+
|
134
|
+
arg.extend([
|
135
|
+
'--owner', job_config['owner'],
|
136
|
+
'--archive', job_config['archive_path'],
|
137
|
+
'--name', job_config['name'],
|
138
|
+
])
|
139
|
+
if job_config['description'] is not None:
|
140
|
+
arg.extend(['--description', job_config['description']])
|
141
|
+
job_archive = os.path.join(job_config['archive_path'], 'orig.config.yaml')
|
142
|
+
arg.extend(['--', job_archive])
|
143
|
+
|
144
|
+
log.debug("Running: %s" % ' '.join(arg))
|
145
|
+
p = subprocess.Popen(
|
146
|
+
args=arg,
|
147
|
+
stdout=subprocess.DEVNULL,
|
148
|
+
stderr=subprocess.DEVNULL,
|
149
|
+
)
|
150
|
+
log.info("Job archive: %s", job_config['archive_path'])
|
151
|
+
log.info("Job PID: %s", str(p.pid))
|
152
|
+
|
153
|
+
if teuth_config.results_server:
|
154
|
+
log.info("Running with watchdog")
|
155
|
+
try:
|
156
|
+
run_with_watchdog(p, job_config)
|
157
|
+
except Exception:
|
158
|
+
log.exception("run_with_watchdog had an unhandled exception")
|
159
|
+
raise
|
160
|
+
else:
|
161
|
+
log.info("Running without watchdog")
|
162
|
+
# This sleep() is to give the child time to start up and create the
|
163
|
+
# archive dir.
|
164
|
+
time.sleep(5)
|
165
|
+
p.wait()
|
166
|
+
|
167
|
+
if p.returncode != 0:
|
168
|
+
log.error('Child exited with code %d', p.returncode)
|
169
|
+
else:
|
170
|
+
log.info('Success!')
|
171
|
+
if 'targets' in job_config:
|
172
|
+
unlock_targets(job_config)
|
173
|
+
return p.returncode
|
174
|
+
|
175
|
+
def failure_is_reimage(failure_reason):
|
176
|
+
if not failure_reason:
|
177
|
+
return False
|
178
|
+
reimage_failure = "Error reimaging machines:"
|
179
|
+
if reimage_failure in failure_reason:
|
180
|
+
return True
|
181
|
+
else:
|
182
|
+
return False
|
183
|
+
|
184
|
+
|
185
|
+
def check_for_reimage_failures_and_mark_down(targets, count=10):
|
186
|
+
# Grab paddles history of jobs in the machine
|
187
|
+
# and count the number of reimaging errors
|
188
|
+
# if it fails N times then mark the machine down
|
189
|
+
base_url = teuth_config.results_server
|
190
|
+
for k, _ in targets.items():
|
191
|
+
machine = k.split('@')[-1]
|
192
|
+
url = urljoin(
|
193
|
+
base_url,
|
194
|
+
'/nodes/{0}/jobs/?count={1}'.format(machine, count)
|
195
|
+
)
|
196
|
+
resp = requests.get(url)
|
197
|
+
jobs = resp.json()
|
198
|
+
if len(jobs) < count:
|
199
|
+
continue
|
200
|
+
reimage_failures = list(filter(
|
201
|
+
lambda j: failure_is_reimage(j['failure_reason']),
|
202
|
+
jobs
|
203
|
+
))
|
204
|
+
if len(reimage_failures) < count:
|
205
|
+
continue
|
206
|
+
# Mark machine down
|
207
|
+
machine_name = shortname(k)
|
208
|
+
lock_ops.update_lock(
|
209
|
+
machine_name,
|
210
|
+
description='reimage failed {0} times'.format(count),
|
211
|
+
status='down',
|
212
|
+
)
|
213
|
+
log.error(
|
214
|
+
'Reimage failed {0} times ... marking machine down'.format(count)
|
215
|
+
)
|
216
|
+
|
217
|
+
|
218
|
+
def reimage(job_config):
|
219
|
+
# Reimage the targets specified in job config
|
220
|
+
# and update their keys in config after reimaging
|
221
|
+
ctx = create_fake_context(job_config)
|
222
|
+
# change the status during the reimaging process
|
223
|
+
report.try_push_job_info(ctx.config, dict(status='waiting'))
|
224
|
+
targets = job_config['targets']
|
225
|
+
try:
|
226
|
+
reimaged = lock_ops.reimage_machines(ctx, targets, job_config['machine_type'])
|
227
|
+
except Exception as e:
|
228
|
+
log.exception('Reimaging error. Nuking machines...')
|
229
|
+
# Reimage failures should map to the 'dead' status instead of 'fail'
|
230
|
+
report.try_push_job_info(
|
231
|
+
ctx.config,
|
232
|
+
dict(status='dead', failure_reason='Error reimaging machines: ' + str(e))
|
233
|
+
)
|
234
|
+
# There isn't an actual task called "reimage", but it doesn't seem
|
235
|
+
# necessary to create a whole new Sentry tag for this.
|
236
|
+
ctx.summary = {
|
237
|
+
'sentry_event': sentry.report_error(job_config, e, task_name="reimage")
|
238
|
+
}
|
239
|
+
# Machine that fails to reimage after 10 times will be marked down
|
240
|
+
check_for_reimage_failures_and_mark_down(targets)
|
241
|
+
raise
|
242
|
+
ctx.config['targets'] = reimaged
|
243
|
+
# change the status to running after the reimaging process
|
244
|
+
report.try_push_job_info(ctx.config, dict(status='running'))
|
245
|
+
|
246
|
+
|
247
|
+
def unlock_targets(job_config):
|
248
|
+
serializer = report.ResultsSerializer(teuth_config.archive_base)
|
249
|
+
job_info = serializer.job_info(job_config['name'], job_config['job_id'])
|
250
|
+
machine_statuses = query.get_statuses(job_info['targets'].keys())
|
251
|
+
# only unlock targets if locked and description matches
|
252
|
+
locked = []
|
253
|
+
for status in machine_statuses:
|
254
|
+
name = shortname(status['name'])
|
255
|
+
description = status['description']
|
256
|
+
if not status['locked']:
|
257
|
+
continue
|
258
|
+
if description != job_info['archive_path']:
|
259
|
+
log.warning(
|
260
|
+
"Was going to unlock %s but it was locked by another job: %s",
|
261
|
+
name, description
|
262
|
+
)
|
263
|
+
continue
|
264
|
+
locked.append(name)
|
265
|
+
if not locked:
|
266
|
+
return
|
267
|
+
if job_config.get("unlock_on_failure", True):
|
268
|
+
log.info('Unlocking machines...')
|
269
|
+
lock_ops.unlock_safe(locked, job_info["owner"], job_info["name"], job_info["job_id"])
|
270
|
+
|
271
|
+
|
272
|
+
def run_with_watchdog(process, job_config):
|
273
|
+
job_start_time = datetime.datetime.now(datetime.timezone.utc)
|
274
|
+
|
275
|
+
# Only push the information that's relevant to the watchdog, to save db
|
276
|
+
# load
|
277
|
+
job_info = dict(
|
278
|
+
name=job_config['name'],
|
279
|
+
job_id=job_config['job_id'],
|
280
|
+
)
|
281
|
+
|
282
|
+
# Sleep once outside of the loop to avoid double-posting jobs
|
283
|
+
time.sleep(teuth_config.watchdog_interval)
|
284
|
+
hit_max_timeout = False
|
285
|
+
while process.poll() is None:
|
286
|
+
# Kill jobs that have been running longer than the global max
|
287
|
+
run_time = datetime.datetime.now(datetime.timezone.utc) - job_start_time
|
288
|
+
total_seconds = run_time.days * 60 * 60 * 24 + run_time.seconds
|
289
|
+
if total_seconds > teuth_config.max_job_time:
|
290
|
+
hit_max_timeout = True
|
291
|
+
log.warning("Job ran longer than {max}s. Killing...".format(
|
292
|
+
max=teuth_config.max_job_time))
|
293
|
+
try:
|
294
|
+
# kill processes but do not unlock yet so we can save
|
295
|
+
# the logs, coredumps, etc.
|
296
|
+
kill.kill_job(
|
297
|
+
job_info['name'], job_info['job_id'],
|
298
|
+
teuth_config.archive_base, job_config['owner'],
|
299
|
+
skip_unlock=True
|
300
|
+
)
|
301
|
+
except Exception:
|
302
|
+
log.exception('Failed to kill job')
|
303
|
+
|
304
|
+
try:
|
305
|
+
transfer_archives(job_info['name'], job_info['job_id'],
|
306
|
+
teuth_config.archive_base, job_config)
|
307
|
+
except Exception:
|
308
|
+
log.exception('Could not save logs')
|
309
|
+
|
310
|
+
try:
|
311
|
+
# this time remove everything and unlock the machines
|
312
|
+
kill.kill_job(
|
313
|
+
job_info['name'], job_info['job_id'],
|
314
|
+
teuth_config.archive_base, job_config['owner']
|
315
|
+
)
|
316
|
+
except Exception:
|
317
|
+
log.exception('Failed to kill job and unlock machines')
|
318
|
+
|
319
|
+
# calling this without a status just updates the jobs updated time
|
320
|
+
try:
|
321
|
+
report.try_push_job_info(job_info)
|
322
|
+
except MaxWhileTries:
|
323
|
+
log.exception("Failed to report job status; ignoring")
|
324
|
+
time.sleep(teuth_config.watchdog_interval)
|
325
|
+
|
326
|
+
# we no longer support testing theses old branches
|
327
|
+
assert(job_config.get('teuthology_branch') not in ('argonaut', 'bobtail',
|
328
|
+
'cuttlefish', 'dumpling'))
|
329
|
+
|
330
|
+
# Let's make sure that paddles knows the job is finished. We don't know
|
331
|
+
# the status, but if it was a pass or fail it will have already been
|
332
|
+
# reported to paddles. In that case paddles ignores the 'dead' status.
|
333
|
+
# If the job was killed, paddles will use the 'dead' status.
|
334
|
+
extra_info = dict(status='dead')
|
335
|
+
if hit_max_timeout:
|
336
|
+
extra_info['failure_reason'] = 'hit max job timeout'
|
337
|
+
if not (job_config.get('first_in_suite') or job_config.get('last_in_suite')):
|
338
|
+
report.try_push_job_info(job_info, extra_info)
|
339
|
+
|
340
|
+
|
341
|
+
def create_fake_context(job_config, block=False):
|
342
|
+
owner = job_config.get('owner', get_user())
|
343
|
+
os_version = job_config.get('os_version', None)
|
344
|
+
|
345
|
+
ctx_args = {
|
346
|
+
'config': job_config,
|
347
|
+
'block': block,
|
348
|
+
'owner': owner,
|
349
|
+
'archive': job_config['archive_path'],
|
350
|
+
'machine_type': job_config['machine_type'],
|
351
|
+
'os_type': job_config.get('os_type', 'ubuntu'),
|
352
|
+
'os_version': os_version,
|
353
|
+
'name': job_config['name'],
|
354
|
+
'job_id': job_config['job_id'],
|
355
|
+
}
|
356
|
+
|
357
|
+
return FakeNamespace(ctx_args)
|
358
|
+
|
359
|
+
|
360
|
+
def transfer_archives(run_name, job_id, archive_base, job_config):
|
361
|
+
serializer = report.ResultsSerializer(archive_base)
|
362
|
+
job_info = serializer.job_info(run_name, job_id, simple=True)
|
363
|
+
|
364
|
+
if 'archive' in job_info:
|
365
|
+
ctx = create_fake_context(job_config)
|
366
|
+
internal.add_remotes(ctx, job_config)
|
367
|
+
|
368
|
+
for log_type, log_path in job_info['archive'].items():
|
369
|
+
if log_type == 'init':
|
370
|
+
log_type = ''
|
371
|
+
compress_logs(ctx, log_path)
|
372
|
+
archive_logs(ctx, log_path, log_type)
|
373
|
+
else:
|
374
|
+
log.info('No archives to transfer.')
|
teuthology/exceptions.py
CHANGED
@@ -12,6 +12,18 @@ class BranchNotFoundError(ValueError):
|
|
12
12
|
branch=self.branch, repo_str=repo_str)
|
13
13
|
|
14
14
|
|
15
|
+
class BranchMismatchError(ValueError):
|
16
|
+
def __init__(self, branch, repo, reason=None):
|
17
|
+
self.branch = branch
|
18
|
+
self.repo = repo
|
19
|
+
self.reason = reason
|
20
|
+
|
21
|
+
def __str__(self):
|
22
|
+
msg = f"Cannot use branch {self.branch} with repo {self.repo}"
|
23
|
+
if self.reason:
|
24
|
+
msg = f"{msg} because {self.reason}"
|
25
|
+
return msg
|
26
|
+
|
15
27
|
class CommitNotFoundError(ValueError):
|
16
28
|
def __init__(self, commit, repo=None):
|
17
29
|
self.commit = commit
|
@@ -68,6 +80,17 @@ class CommandFailedError(Exception):
|
|
68
80
|
prefix=prefix,
|
69
81
|
)
|
70
82
|
|
83
|
+
def fingerprint(self):
|
84
|
+
"""
|
85
|
+
Returns a list of strings to group failures with.
|
86
|
+
Used by sentry instead of grouping by backtrace.
|
87
|
+
"""
|
88
|
+
return [
|
89
|
+
self.label or self.command,
|
90
|
+
'exit status {}'.format(self.exitstatus),
|
91
|
+
'{{ type }}',
|
92
|
+
]
|
93
|
+
|
71
94
|
|
72
95
|
class AnsibleFailedError(Exception):
|
73
96
|
|
@@ -82,6 +105,13 @@ class AnsibleFailedError(Exception):
|
|
82
105
|
failures=self.failures,
|
83
106
|
)
|
84
107
|
|
108
|
+
def fingerprint(self):
|
109
|
+
"""
|
110
|
+
Sentry will use this to group events by their failure reasons, rather
|
111
|
+
than lumping all AnsibleFailedErrors together
|
112
|
+
"""
|
113
|
+
return self.failures
|
114
|
+
|
85
115
|
|
86
116
|
class CommandCrashedError(Exception):
|
87
117
|
|
@@ -182,3 +212,27 @@ class NoRemoteError(Exception):
|
|
182
212
|
|
183
213
|
def __str__(self):
|
184
214
|
return self.message
|
215
|
+
|
216
|
+
|
217
|
+
class UnitTestError(Exception):
|
218
|
+
"""
|
219
|
+
Exception thrown on unit test failure
|
220
|
+
"""
|
221
|
+
def __init__(self, exitstatus=None, node=None, label=None, message=None):
|
222
|
+
self.exitstatus = exitstatus
|
223
|
+
self.node = node
|
224
|
+
self.label = label
|
225
|
+
self.message = message
|
226
|
+
|
227
|
+
def __str__(self):
|
228
|
+
prefix = "Unit test failed"
|
229
|
+
if self.label:
|
230
|
+
prefix += " ({label})".format(label=self.label)
|
231
|
+
if self.node:
|
232
|
+
prefix += " on {node}".format(node=self.node)
|
233
|
+
if self.exitstatus:
|
234
|
+
prefix += " with status {status}".format(status=self.exitstatus)
|
235
|
+
return "{prefix}: '{message}'".format(
|
236
|
+
prefix=prefix,
|
237
|
+
message=self.message,
|
238
|
+
)
|