teuthology 1.1.0__py3-none-any.whl → 1.2.1__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 +55 -26
- 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/schedule.py +4 -0
- scripts/suite.py +57 -16
- scripts/supervisor.py +44 -0
- scripts/update_inventory.py +10 -4
- teuthology/__init__.py +24 -26
- teuthology/beanstalk.py +4 -3
- teuthology/config.py +16 -6
- teuthology/contextutil.py +18 -14
- teuthology/describe_tests.py +25 -18
- teuthology/dispatcher/__init__.py +210 -35
- teuthology/dispatcher/supervisor.py +140 -58
- teuthology/exceptions.py +43 -0
- teuthology/exporter.py +347 -0
- teuthology/kill.py +76 -81
- teuthology/lock/cli.py +3 -3
- teuthology/lock/ops.py +135 -61
- teuthology/lock/query.py +61 -44
- teuthology/ls.py +1 -1
- teuthology/misc.py +61 -75
- teuthology/nuke/__init__.py +12 -353
- 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 +49 -7
- teuthology/orchestra/connection.py +17 -4
- teuthology/orchestra/console.py +111 -50
- teuthology/orchestra/daemon/cephadmunit.py +15 -2
- teuthology/orchestra/daemon/state.py +8 -1
- teuthology/orchestra/daemon/systemd.py +4 -4
- teuthology/orchestra/opsys.py +30 -11
- teuthology/orchestra/remote.py +405 -338
- teuthology/orchestra/run.py +3 -3
- teuthology/packaging.py +19 -16
- teuthology/provision/__init__.py +30 -10
- teuthology/provision/cloud/openstack.py +12 -6
- teuthology/provision/cloud/util.py +1 -2
- teuthology/provision/downburst.py +4 -3
- teuthology/provision/fog.py +68 -20
- teuthology/provision/openstack.py +5 -4
- teuthology/provision/pelagos.py +1 -1
- teuthology/repo_utils.py +43 -13
- teuthology/report.py +57 -35
- teuthology/results.py +5 -3
- teuthology/run.py +13 -14
- teuthology/run_tasks.py +27 -43
- teuthology/schedule.py +4 -3
- teuthology/scrape.py +28 -22
- teuthology/suite/__init__.py +74 -45
- 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 +6 -9
- teuthology/suite/run.py +175 -100
- teuthology/suite/util.py +64 -218
- teuthology/task/__init__.py +1 -1
- teuthology/task/ansible.py +101 -32
- 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 +29 -7
- 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 +1 -1
- teuthology/task/install/rpm.py +17 -5
- teuthology/task/install/util.py +3 -3
- teuthology/task/internal/__init__.py +41 -10
- teuthology/task/internal/edit_sudoers.sh +10 -0
- teuthology/task/internal/lock_machines.py +2 -9
- teuthology/task/internal/redhat.py +31 -1
- teuthology/task/internal/syslog.py +31 -8
- teuthology/task/kernel.py +152 -145
- teuthology/task/lockfile.py +1 -1
- teuthology/task/mpi.py +10 -10
- teuthology/task/pcp.py +1 -1
- teuthology/task/selinux.py +16 -8
- teuthology/task/ssh_keys.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.1.data/scripts/adjust-ulimits +16 -0
- teuthology-1.2.1.data/scripts/daemon-helper +114 -0
- teuthology-1.2.1.data/scripts/stdin-killer +263 -0
- teuthology-1.2.1.dist-info/METADATA +88 -0
- teuthology-1.2.1.dist-info/RECORD +168 -0
- {teuthology-1.1.0.dist-info → teuthology-1.2.1.dist-info}/WHEEL +1 -1
- {teuthology-1.1.0.dist-info → teuthology-1.2.1.dist-info}/entry_points.txt +3 -2
- scripts/nuke.py +0 -47
- scripts/worker.py +0 -37
- teuthology/lock/test/__init__.py +0 -0
- teuthology/lock/test/test_lock.py +0 -7
- 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/task/tests/__init__.py +0 -110
- teuthology/task/tests/test_locking.py +0 -25
- teuthology/task/tests/test_run.py +0 -40
- 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 -391
- teuthology/test/test_nuke.py +0 -290
- teuthology/test/test_packaging.py +0 -763
- teuthology/test/test_parallel.py +0 -28
- teuthology/test/test_repo_utils.py +0 -225
- teuthology/test/test_report.py +0 -77
- teuthology/test/test_results.py +0 -155
- teuthology/test/test_run.py +0 -239
- 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 -354
- teuthology-1.1.0.dist-info/METADATA +0 -76
- teuthology-1.1.0.dist-info/RECORD +0 -213
- {teuthology-1.1.0.dist-info → teuthology-1.2.1.dist-info}/LICENSE +0 -0
- {teuthology-1.1.0.dist-info → teuthology-1.2.1.dist-info}/top_level.txt +0 -0
teuthology/report.py
CHANGED
@@ -4,11 +4,14 @@ import json
|
|
4
4
|
import re
|
5
5
|
import requests
|
6
6
|
import logging
|
7
|
+
import random
|
7
8
|
import socket
|
8
9
|
from datetime import datetime
|
9
10
|
|
10
11
|
import teuthology
|
12
|
+
import teuthology.exporter
|
11
13
|
from teuthology.config import config
|
14
|
+
from teuthology.contextutil import safe_while
|
12
15
|
from teuthology.job_status import get_status, set_status
|
13
16
|
|
14
17
|
report_exceptions = (requests.exceptions.RequestException, socket.error)
|
@@ -141,7 +144,7 @@ class ResultsSerializer(object):
|
|
141
144
|
return {}
|
142
145
|
jobs = {}
|
143
146
|
for item in os.listdir(archive_dir):
|
144
|
-
if not re.match('\d+$', item):
|
147
|
+
if not re.match(r'\d+$', item):
|
145
148
|
continue
|
146
149
|
job_id = item
|
147
150
|
job_dir = os.path.join(archive_dir, job_id)
|
@@ -197,7 +200,7 @@ class ResultsReporter(object):
|
|
197
200
|
|
198
201
|
if not self.base_uri:
|
199
202
|
msg = "No results_server set in {yaml}; cannot report results"
|
200
|
-
self.log.
|
203
|
+
self.log.warning(msg.format(yaml=config.yaml_path))
|
201
204
|
|
202
205
|
def _make_session(self, max_retries=10):
|
203
206
|
session = requests.Session()
|
@@ -289,37 +292,42 @@ class ResultsReporter(object):
|
|
289
292
|
set_status(job_info, 'dead')
|
290
293
|
job_json = json.dumps(job_info)
|
291
294
|
headers = {'content-type': 'application/json'}
|
292
|
-
response = self.session.post(run_uri, data=job_json, headers=headers)
|
293
295
|
|
294
|
-
|
295
|
-
|
296
|
+
inc = random.uniform(0, 1)
|
297
|
+
with safe_while(
|
298
|
+
sleep=1, increment=inc, action=f'report job {job_id}') as proceed:
|
299
|
+
while proceed():
|
300
|
+
response = self.session.post(run_uri, data=job_json, headers=headers)
|
296
301
|
|
297
|
-
|
298
|
-
|
299
|
-
try:
|
300
|
-
resp_json = response.json()
|
301
|
-
except ValueError:
|
302
|
-
resp_json = dict()
|
302
|
+
if response.status_code == 200:
|
303
|
+
return
|
303
304
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
job_uri = os.path.join(run_uri, job_id, '')
|
311
|
-
response = self.session.put(job_uri, data=job_json,
|
312
|
-
headers=headers)
|
313
|
-
elif msg:
|
314
|
-
self.log.error(
|
315
|
-
"POST to {uri} failed with status {status}: {msg}".format(
|
316
|
-
uri=run_uri,
|
317
|
-
status=response.status_code,
|
318
|
-
msg=msg,
|
319
|
-
))
|
320
|
-
response.raise_for_status()
|
305
|
+
# This call is wrapped in a try/except because of:
|
306
|
+
# http://tracker.ceph.com/issues/8166
|
307
|
+
try:
|
308
|
+
resp_json = response.json()
|
309
|
+
except ValueError:
|
310
|
+
resp_json = dict()
|
321
311
|
|
322
|
-
|
312
|
+
if resp_json:
|
313
|
+
msg = resp_json.get('message', '')
|
314
|
+
else:
|
315
|
+
msg = response.text
|
316
|
+
|
317
|
+
if msg and msg.endswith('already exists'):
|
318
|
+
job_uri = os.path.join(run_uri, job_id, '')
|
319
|
+
response = self.session.put(job_uri, data=job_json,
|
320
|
+
headers=headers)
|
321
|
+
if response.status_code == 200:
|
322
|
+
return
|
323
|
+
elif msg:
|
324
|
+
self.log.error(
|
325
|
+
"POST to {uri} failed with status {status}: {msg}".format(
|
326
|
+
uri=run_uri,
|
327
|
+
status=response.status_code,
|
328
|
+
msg=msg,
|
329
|
+
))
|
330
|
+
response.raise_for_status()
|
323
331
|
|
324
332
|
@property
|
325
333
|
def last_run(self):
|
@@ -394,6 +402,7 @@ class ResultsReporter(object):
|
|
394
402
|
log_path = os.path.join(self.archive_base, run_name, 'results.log')
|
395
403
|
# parse the log file generated by teuthology.results.results()
|
396
404
|
subset = None
|
405
|
+
no_nested_subset = None
|
397
406
|
seed = None
|
398
407
|
with open(log_path) as results_log:
|
399
408
|
for line in results_log:
|
@@ -403,15 +412,17 @@ class ResultsReporter(object):
|
|
403
412
|
line = line.strip()
|
404
413
|
if subset is None:
|
405
414
|
subset = self._parse_log_line(line, 'subset:')
|
406
|
-
|
415
|
+
if no_nested_subset is None:
|
416
|
+
no_nested_subset = self._parse_log_line(line, 'no_nested_subset:')
|
417
|
+
if seed is None:
|
407
418
|
seed = self._parse_log_line(line, 'seed:')
|
408
|
-
else:
|
409
|
-
break
|
410
419
|
if subset is not None:
|
411
420
|
subset = tuple(int(i) for i in subset.split('/'))
|
421
|
+
if no_nested_subset is not None:
|
422
|
+
no_nested_subset = bool(no_nested_subset)
|
412
423
|
if seed is not None:
|
413
424
|
seed = int(seed)
|
414
|
-
return subset, seed
|
425
|
+
return subset, no_nested_subset, seed
|
415
426
|
|
416
427
|
def delete_job(self, run_name, job_id):
|
417
428
|
"""
|
@@ -461,6 +472,12 @@ def push_job_info(run_name, job_id, job_info, base_uri=None):
|
|
461
472
|
if not reporter.base_uri:
|
462
473
|
return
|
463
474
|
reporter.report_job(run_name, job_id, job_info)
|
475
|
+
status = get_status(job_info)
|
476
|
+
if status in ["pass", "fail", "dead"] and "machine_type" in job_info:
|
477
|
+
teuthology.exporter.JobResults().record(
|
478
|
+
machine_type=job_info["machine_type"],
|
479
|
+
status=status,
|
480
|
+
)
|
464
481
|
|
465
482
|
|
466
483
|
def try_push_job_info(job_config, extra_info=None):
|
@@ -488,8 +505,8 @@ def try_push_job_info(job_config, extra_info=None):
|
|
488
505
|
job_id = job_config['job_id']
|
489
506
|
|
490
507
|
if extra_info is not None:
|
491
|
-
job_info =
|
492
|
-
job_info.update(
|
508
|
+
job_info = job_config.copy()
|
509
|
+
job_info.update(extra_info)
|
493
510
|
else:
|
494
511
|
job_info = job_config
|
495
512
|
|
@@ -569,6 +586,11 @@ def try_mark_run_dead(run_name):
|
|
569
586
|
try:
|
570
587
|
log.info("Marking job {job_id} as dead".format(job_id=job_id))
|
571
588
|
reporter.report_job(run_name, job['job_id'], dead=True)
|
589
|
+
if "machine_type" in job:
|
590
|
+
teuthology.exporter.JobResults().record(
|
591
|
+
machine_type=job["machine_type"],
|
592
|
+
status=job["status"],
|
593
|
+
)
|
572
594
|
except report_exceptions:
|
573
595
|
log.exception("Could not mark job as dead: {job_id}".format(
|
574
596
|
job_id=job_id))
|
teuthology/results.py
CHANGED
@@ -28,7 +28,7 @@ def main(args):
|
|
28
28
|
|
29
29
|
try:
|
30
30
|
if args['--seed']:
|
31
|
-
note_rerun_params(args['--subset'], args['--seed'])
|
31
|
+
note_rerun_params(args['--subset'], args['--no-nested-subset'], args['--seed'])
|
32
32
|
else:
|
33
33
|
results(args['--archive-dir'], args['--name'], args['--email'],
|
34
34
|
int(args['--timeout']), args['--dry-run'])
|
@@ -37,9 +37,11 @@ def main(args):
|
|
37
37
|
raise
|
38
38
|
|
39
39
|
|
40
|
-
def note_rerun_params(subset, seed):
|
40
|
+
def note_rerun_params(subset, no_nested_subset, seed):
|
41
41
|
if subset:
|
42
42
|
log.info('subset: %r', subset)
|
43
|
+
if no_nested_subset:
|
44
|
+
log.info('no_nested_subset: %r', no_nested_subset)
|
43
45
|
if seed:
|
44
46
|
log.info('seed: %r', seed)
|
45
47
|
|
@@ -53,7 +55,7 @@ def results(archive_dir, name, email, timeout, dry_run):
|
|
53
55
|
reporter = ResultsReporter()
|
54
56
|
while timeout > 0:
|
55
57
|
if time.time() - starttime > timeout:
|
56
|
-
log.
|
58
|
+
log.warning('test(s) did not finish before timeout of %d seconds',
|
57
59
|
timeout)
|
58
60
|
break
|
59
61
|
jobs = reporter.get_jobs(name, fields=['job_id', 'status'])
|
teuthology/run.py
CHANGED
@@ -8,7 +8,6 @@ from teuthology import install_except_hook
|
|
8
8
|
from teuthology import report
|
9
9
|
from teuthology.job_status import get_status
|
10
10
|
from teuthology.misc import get_user, merge_configs
|
11
|
-
from teuthology.nuke import nuke
|
12
11
|
from teuthology.run_tasks import run_tasks
|
13
12
|
from teuthology.repo_utils import fetch_qa_suite
|
14
13
|
from teuthology.results import email_results
|
@@ -80,7 +79,7 @@ def fetch_tasks_if_needed(job_config):
|
|
80
79
|
except ImportError:
|
81
80
|
log.info("Tasks not found; will attempt to fetch")
|
82
81
|
|
83
|
-
ceph_branch = job_config.get('branch', '
|
82
|
+
ceph_branch = job_config.get('branch', 'main')
|
84
83
|
suite_repo = job_config.get('suite_repo')
|
85
84
|
if suite_repo:
|
86
85
|
teuth_config.ceph_qa_suite_git_url = suite_repo
|
@@ -88,13 +87,13 @@ def fetch_tasks_if_needed(job_config):
|
|
88
87
|
suite_sha1 = job_config.get('suite_sha1')
|
89
88
|
suite_path = os.path.normpath(os.path.join(
|
90
89
|
fetch_qa_suite(suite_branch, commit=suite_sha1),
|
91
|
-
job_config.get('suite_relpath', ''),
|
90
|
+
job_config.get('suite_relpath', 'qa'),
|
92
91
|
))
|
93
92
|
sys.path.insert(1, suite_path)
|
94
93
|
return suite_path
|
95
94
|
|
96
95
|
|
97
|
-
def setup_config(config_paths):
|
96
|
+
def setup_config(config_paths) -> dict:
|
98
97
|
"""
|
99
98
|
Takes a list of config yaml files and combines them
|
100
99
|
into a single dictionary. Processes / validates the dictionary and then
|
@@ -250,7 +249,9 @@ def get_initial_tasks(lock, config, machine_type):
|
|
250
249
|
{'internal.git_ignore_ssl': None},
|
251
250
|
{'internal.setup_cdn_repo': None},
|
252
251
|
{'internal.setup_base_repo': None},
|
253
|
-
{'internal.setup_additional_repo': None}
|
252
|
+
{'internal.setup_additional_repo': None},
|
253
|
+
{'internal.setup_container_registry': None},
|
254
|
+
{'install': None},
|
254
255
|
])
|
255
256
|
# Install latest kernel task for redhat downstream runs
|
256
257
|
if config.get('redhat').get('install_latest_rh_kernel', False):
|
@@ -259,15 +260,11 @@ def get_initial_tasks(lock, config, machine_type):
|
|
259
260
|
return init_tasks
|
260
261
|
|
261
262
|
|
262
|
-
def report_outcome(config, archive, summary
|
263
|
+
def report_outcome(config, archive, summary):
|
263
264
|
""" Reports on the final outcome of the command. """
|
264
265
|
status = get_status(summary)
|
265
266
|
passed = status == 'pass'
|
266
267
|
|
267
|
-
if not passed and bool(config.get('nuke-on-error')):
|
268
|
-
# only unlock if we locked them in the first place
|
269
|
-
nuke(fake_ctx, fake_ctx.lock)
|
270
|
-
|
271
268
|
if archive is not None:
|
272
269
|
with open(os.path.join(archive, 'summary.yaml'), 'w') as f:
|
273
270
|
yaml.safe_dump(summary, f, default_flow_style=False)
|
@@ -329,8 +326,6 @@ def main(args):
|
|
329
326
|
os_version = args["--os-version"]
|
330
327
|
interactive_on_error = args["--interactive-on-error"]
|
331
328
|
|
332
|
-
set_up_logging(verbose, archive)
|
333
|
-
|
334
329
|
# print the command being ran
|
335
330
|
log.debug("Teuthology command: {0}".format(get_teuthology_command(args)))
|
336
331
|
|
@@ -341,6 +336,10 @@ def main(args):
|
|
341
336
|
|
342
337
|
if archive is not None and 'archive_path' not in config:
|
343
338
|
config['archive_path'] = archive
|
339
|
+
elif archive is None and 'archive_path' in config:
|
340
|
+
archive = args['--archive'] = config['archive_path']
|
341
|
+
|
342
|
+
set_up_logging(verbose, archive)
|
344
343
|
|
345
344
|
write_initial_metadata(archive, config, name, description, owner)
|
346
345
|
report.try_push_job_info(config, dict(status='running'))
|
@@ -403,10 +402,10 @@ def main(args):
|
|
403
402
|
# FIXME this should become more generic, and the keys should use
|
404
403
|
# '_' uniformly
|
405
404
|
if fake_ctx.config.get('interactive-on-error'):
|
406
|
-
|
405
|
+
teuth_config.config.ctx = fake_ctx
|
407
406
|
|
408
407
|
try:
|
409
408
|
run_tasks(tasks=config['tasks'], ctx=fake_ctx)
|
410
409
|
finally:
|
411
410
|
# print to stdout the results and possibly send an email on any errors
|
412
|
-
report_outcome(config, archive, fake_ctx.summary
|
411
|
+
report_outcome(config, archive, fake_ctx.summary)
|
teuthology/run_tasks.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import importlib
|
1
2
|
import jinja2
|
2
3
|
import logging
|
3
4
|
import os
|
@@ -6,15 +7,16 @@ import time
|
|
6
7
|
import types
|
7
8
|
import yaml
|
8
9
|
|
9
|
-
from copy import deepcopy
|
10
10
|
from humanfriendly import format_timespan
|
11
|
-
|
11
|
+
|
12
|
+
import teuthology.exporter as exporter
|
12
13
|
|
13
14
|
from teuthology.config import config as teuth_config
|
14
15
|
from teuthology.exceptions import ConnectionLostError
|
15
16
|
from teuthology.job_status import set_status, get_status
|
16
17
|
from teuthology.misc import get_http_log_path, get_results_url
|
17
18
|
from teuthology.timer import Timer
|
19
|
+
from teuthology.util import sentry
|
18
20
|
|
19
21
|
log = logging.getLogger(__name__)
|
20
22
|
|
@@ -60,6 +62,17 @@ def _import(from_package, module_name, task_name, fail_on_import_error=False):
|
|
60
62
|
if fail_on_import_error:
|
61
63
|
raise
|
62
64
|
else:
|
65
|
+
if (
|
66
|
+
importlib.util.find_spec(from_package) is not None and
|
67
|
+
importlib.util.find_spec(full_module_name) is not None
|
68
|
+
):
|
69
|
+
# If we get here, it means we could _find_ both the module and
|
70
|
+
# the package that contains it, but still got an ImportError.
|
71
|
+
# Typically that means the module failed to import because it
|
72
|
+
# could not find one of its dependencies; if we don't raise
|
73
|
+
# here it will look like we just could't find the module,
|
74
|
+
# making the dependency issue difficult to discover.
|
75
|
+
raise
|
63
76
|
return None
|
64
77
|
return module
|
65
78
|
|
@@ -80,6 +93,7 @@ def run_tasks(tasks, ctx):
|
|
80
93
|
else:
|
81
94
|
timer = Timer()
|
82
95
|
stack = []
|
96
|
+
taskname = ""
|
83
97
|
try:
|
84
98
|
for taskdict in tasks:
|
85
99
|
try:
|
@@ -91,7 +105,11 @@ def run_tasks(tasks, ctx):
|
|
91
105
|
manager = run_one_task(taskname, ctx=ctx, config=config)
|
92
106
|
if hasattr(manager, '__enter__'):
|
93
107
|
stack.append((taskname, manager))
|
94
|
-
|
108
|
+
with exporter.TaskTime().time(
|
109
|
+
name=taskname,
|
110
|
+
phase="enter"
|
111
|
+
):
|
112
|
+
manager.__enter__()
|
95
113
|
except BaseException as e:
|
96
114
|
if isinstance(e, ConnectionLostError):
|
97
115
|
# Prevent connection issues being flagged as failures
|
@@ -104,45 +122,7 @@ def run_tasks(tasks, ctx):
|
|
104
122
|
ctx.summary['failure_reason'] = str(e)
|
105
123
|
log.exception('Saw exception from tasks.')
|
106
124
|
|
107
|
-
|
108
|
-
sentry_sdk.init(teuth_config.sentry_dsn)
|
109
|
-
config = deepcopy(ctx.config)
|
110
|
-
|
111
|
-
tags = {
|
112
|
-
'task': taskname,
|
113
|
-
'owner': ctx.owner,
|
114
|
-
}
|
115
|
-
optional_tags = ('teuthology_branch', 'branch', 'suite',
|
116
|
-
'machine_type', 'os_type', 'os_version')
|
117
|
-
for tag in optional_tags:
|
118
|
-
if tag in config:
|
119
|
-
tags[tag] = config[tag]
|
120
|
-
|
121
|
-
# Remove ssh keys from reported config
|
122
|
-
if 'targets' in config:
|
123
|
-
targets = config['targets']
|
124
|
-
for host in targets.keys():
|
125
|
-
targets[host] = '<redacted>'
|
126
|
-
|
127
|
-
job_id = ctx.config.get('job_id')
|
128
|
-
archive_path = ctx.config.get('archive_path')
|
129
|
-
extras = dict(config=config,
|
130
|
-
)
|
131
|
-
if job_id:
|
132
|
-
extras['logs'] = get_http_log_path(archive_path, job_id)
|
133
|
-
|
134
|
-
fingerprint = e.fingerprint() if hasattr(e, 'fingerprint') else None
|
135
|
-
exc_id = sentry_sdk.capture_exception(
|
136
|
-
error=e,
|
137
|
-
tags=tags,
|
138
|
-
extras=extras,
|
139
|
-
fingerprint=fingerprint,
|
140
|
-
)
|
141
|
-
event_url = "{server}/?query={id}".format(
|
142
|
-
server=teuth_config.sentry_server.strip('/'), id=exc_id)
|
143
|
-
log.exception(" Sentry event: %s" % event_url)
|
144
|
-
ctx.summary['sentry_event'] = event_url
|
145
|
-
|
125
|
+
ctx.summary['sentry_event'] = sentry.report_error(ctx.config, e, taskname)
|
146
126
|
if ctx.config.get('interactive-on-error'):
|
147
127
|
ctx.config['interactive-on-error'] = False
|
148
128
|
from teuthology.task import interactive
|
@@ -173,7 +153,11 @@ def run_tasks(tasks, ctx):
|
|
173
153
|
log.debug('Unwinding manager %s', taskname)
|
174
154
|
timer.mark('%s exit' % taskname)
|
175
155
|
try:
|
176
|
-
|
156
|
+
with exporter.TaskTime().time(
|
157
|
+
name=taskname,
|
158
|
+
phase="exit"
|
159
|
+
):
|
160
|
+
suppress = manager.__exit__(*exc_info)
|
177
161
|
except Exception as e:
|
178
162
|
if isinstance(e, ConnectionLostError):
|
179
163
|
# Prevent connection issues being flagged as failures
|
teuthology/schedule.py
CHANGED
@@ -8,11 +8,11 @@ from teuthology import report
|
|
8
8
|
|
9
9
|
def main(args):
|
10
10
|
if not args['--first-in-suite']:
|
11
|
-
first_job_args = ['subset', 'seed']
|
11
|
+
first_job_args = ['subset', 'no-nested-subset', 'seed']
|
12
12
|
for arg in first_job_args:
|
13
13
|
opt = '--{arg}'.format(arg=arg)
|
14
14
|
msg_fmt = '{opt} is only applicable to the first job in a suite'
|
15
|
-
if args
|
15
|
+
if args.get(opt):
|
16
16
|
raise ValueError(msg_fmt.format(opt=opt))
|
17
17
|
|
18
18
|
if not args['--last-in-suite']:
|
@@ -78,7 +78,8 @@ def build_config(args):
|
|
78
78
|
job_config.update(conf_dict)
|
79
79
|
for arg,conf in {'--timeout':'results_timeout',
|
80
80
|
'--seed': 'seed',
|
81
|
-
'--subset': 'subset'
|
81
|
+
'--subset': 'subset',
|
82
|
+
'--no-nested-subset': 'no_nested_subset'}.items():
|
82
83
|
val = args.get(arg, None)
|
83
84
|
if val is not None:
|
84
85
|
job_config[conf] = val
|
teuthology/scrape.py
CHANGED
@@ -12,7 +12,6 @@ import re
|
|
12
12
|
import logging
|
13
13
|
import subprocess
|
14
14
|
|
15
|
-
import six
|
16
15
|
|
17
16
|
log = logging.getLogger('scrape')
|
18
17
|
log.addHandler(logging.StreamHandler())
|
@@ -31,11 +30,12 @@ def grep(path, expr):
|
|
31
30
|
"""
|
32
31
|
Call out to native grep rather than feeding massive log files through python line by line
|
33
32
|
"""
|
34
|
-
p = subprocess.Popen(["grep", expr, path], stdout=subprocess.PIPE
|
33
|
+
p = subprocess.Popen(["grep", expr, path], stdout=subprocess.PIPE,
|
34
|
+
universal_newlines=True)
|
35
35
|
p.wait()
|
36
36
|
out, err = p.communicate()
|
37
37
|
if p.returncode == 0:
|
38
|
-
return
|
38
|
+
return out.split("\n")
|
39
39
|
else:
|
40
40
|
return []
|
41
41
|
|
@@ -83,9 +83,9 @@ class GenericReason(Reason):
|
|
83
83
|
else:
|
84
84
|
if "Test failure:" in self.failure_reason:
|
85
85
|
return self.failure_reason == job.get_failure_reason()
|
86
|
-
elif re.search("workunit test (.*)\) on ", self.failure_reason):
|
87
|
-
workunit_name = re.search("workunit test (.*)\) on ", self.failure_reason).group(1)
|
88
|
-
other_match = re.search("workunit test (.*)\) on ", job.get_failure_reason())
|
86
|
+
elif re.search(r"workunit test (.*)\) on ", self.failure_reason):
|
87
|
+
workunit_name = re.search(r"workunit test (.*)\) on ", self.failure_reason).group(1)
|
88
|
+
other_match = re.search(r"workunit test (.*)\) on ", job.get_failure_reason())
|
89
89
|
return other_match is not None and workunit_name == other_match.group(1)
|
90
90
|
else:
|
91
91
|
reason_ratio = difflib.SequenceMatcher(None, self.failure_reason, job.get_failure_reason()).ratio()
|
@@ -198,12 +198,13 @@ class DeadReason(Reason):
|
|
198
198
|
else:
|
199
199
|
# I have BT but he doesn't, so we're different
|
200
200
|
return False
|
201
|
-
|
202
|
-
if self.last_tlog_line
|
201
|
+
last_tlog_line = job.get_last_tlog_line()
|
202
|
+
if self.last_tlog_line is not None and last_tlog_line is not None:
|
203
203
|
ratio = difflib.SequenceMatcher(None, self.last_tlog_line,
|
204
|
-
|
204
|
+
last_tlog_line).ratio()
|
205
205
|
return ratio > 0.5
|
206
|
-
|
206
|
+
else:
|
207
|
+
return self.last_tlog_line == last_tlog_line
|
207
208
|
|
208
209
|
|
209
210
|
class TimeoutReason(Reason):
|
@@ -302,29 +303,34 @@ class Job(object):
|
|
302
303
|
for line in file_obj:
|
303
304
|
# Log prefix from teuthology.log
|
304
305
|
if ".stderr:" in line:
|
306
|
+
# Only captures the error and disregard what ever comes before
|
305
307
|
line = line.split(".stderr:")[1]
|
306
308
|
|
307
|
-
if "FAILED assert" in line:
|
309
|
+
if "FAILED assert" in line or "__ceph_assert_fail" in line:
|
308
310
|
assertion = line.strip()
|
309
311
|
|
310
312
|
if line.startswith(" ceph version"):
|
311
313
|
# The start of a backtrace!
|
312
314
|
bt_lines = [line]
|
313
|
-
elif line.startswith(" NOTE: a copy of the executable")
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
315
|
+
elif line.startswith(" NOTE: a copy of the executable") or \
|
316
|
+
line.strip().endswith("clone()") or \
|
317
|
+
"clone()+0x" in line:
|
318
|
+
# The backtrace terminated, if we have a buffer return it
|
319
|
+
if len(bt_lines):
|
320
|
+
return ("".join(bt_lines)).strip(), assertion
|
321
|
+
else:
|
322
|
+
log.warning("Saw end of BT but not start")
|
319
323
|
elif bt_lines:
|
320
324
|
# We're in a backtrace, push the line onto the list
|
321
325
|
if len(bt_lines) > MAX_BT_LINES:
|
322
|
-
#
|
323
|
-
log.warning("Ignoring
|
326
|
+
# We exceeded MAX_BT_LINES, drop it
|
327
|
+
log.warning("Ignoring backtrace that exceeds MAX_BACKTRACE_LINES: {0}".format(
|
324
328
|
", ".join(bt_lines[0:3])
|
325
329
|
))
|
330
|
+
log.warning("Either the backtrace is too long or we did a bad job of checking for end of backtrace!")
|
326
331
|
bt_lines = []
|
327
|
-
|
332
|
+
else:
|
333
|
+
bt_lines.append(line)
|
328
334
|
|
329
335
|
return None, assertion
|
330
336
|
|
@@ -357,7 +363,7 @@ class Job(object):
|
|
357
363
|
for line in grep(tlog_path, "command crashed with signal"):
|
358
364
|
log.debug("Found a crash indication: {0}".format(line))
|
359
365
|
# tasks.ceph.osd.1.plana82.stderr
|
360
|
-
match = re.search("tasks.ceph.([^\.]+).([^\.]+).([^\.]+).stderr", line)
|
366
|
+
match = re.search(r"tasks.ceph.([^\.]+).([^\.]+).([^\.]+).stderr", line)
|
361
367
|
if not match:
|
362
368
|
log.warning("Not-understood crash indication {0}".format(line))
|
363
369
|
continue
|
@@ -410,7 +416,7 @@ class ValgrindReason(Reason):
|
|
410
416
|
log.warning("Misunderstood line: {0}".format(line))
|
411
417
|
continue
|
412
418
|
err_typ, log_basename = match.groups()
|
413
|
-
svc_typ =
|
419
|
+
svc_typ = log_basename.split(".")[0]
|
414
420
|
if err_typ not in result[svc_typ]:
|
415
421
|
result[svc_typ].append(err_typ)
|
416
422
|
result[svc_typ] = sorted(result[svc_typ])
|