teuthology 1.1.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.
Files changed (168) hide show
  1. scripts/describe.py +1 -0
  2. scripts/dispatcher.py +55 -26
  3. scripts/exporter.py +18 -0
  4. scripts/lock.py +1 -1
  5. scripts/node_cleanup.py +58 -0
  6. scripts/openstack.py +9 -9
  7. scripts/results.py +12 -11
  8. scripts/schedule.py +4 -0
  9. scripts/suite.py +57 -16
  10. scripts/supervisor.py +44 -0
  11. scripts/update_inventory.py +10 -4
  12. teuthology/__init__.py +24 -26
  13. teuthology/beanstalk.py +4 -3
  14. teuthology/config.py +16 -6
  15. teuthology/contextutil.py +18 -14
  16. teuthology/describe_tests.py +25 -18
  17. teuthology/dispatcher/__init__.py +210 -35
  18. teuthology/dispatcher/supervisor.py +140 -58
  19. teuthology/exceptions.py +43 -0
  20. teuthology/exporter.py +347 -0
  21. teuthology/kill.py +76 -81
  22. teuthology/lock/cli.py +3 -3
  23. teuthology/lock/ops.py +135 -61
  24. teuthology/lock/query.py +61 -44
  25. teuthology/ls.py +1 -1
  26. teuthology/misc.py +61 -75
  27. teuthology/nuke/__init__.py +12 -353
  28. teuthology/openstack/__init__.py +4 -3
  29. teuthology/openstack/openstack-centos-7.0-user-data.txt +1 -1
  30. teuthology/openstack/openstack-centos-7.1-user-data.txt +1 -1
  31. teuthology/openstack/openstack-centos-7.2-user-data.txt +1 -1
  32. teuthology/openstack/openstack-debian-8.0-user-data.txt +1 -1
  33. teuthology/openstack/openstack-opensuse-42.1-user-data.txt +1 -1
  34. teuthology/openstack/openstack-teuthology.cron +0 -1
  35. teuthology/orchestra/cluster.py +49 -7
  36. teuthology/orchestra/connection.py +16 -5
  37. teuthology/orchestra/console.py +111 -50
  38. teuthology/orchestra/daemon/cephadmunit.py +17 -4
  39. teuthology/orchestra/daemon/state.py +8 -1
  40. teuthology/orchestra/daemon/systemd.py +4 -4
  41. teuthology/orchestra/opsys.py +30 -11
  42. teuthology/orchestra/remote.py +405 -338
  43. teuthology/orchestra/run.py +3 -3
  44. teuthology/packaging.py +19 -16
  45. teuthology/provision/__init__.py +30 -10
  46. teuthology/provision/cloud/openstack.py +12 -6
  47. teuthology/provision/cloud/util.py +1 -2
  48. teuthology/provision/downburst.py +4 -3
  49. teuthology/provision/fog.py +68 -20
  50. teuthology/provision/openstack.py +5 -4
  51. teuthology/provision/pelagos.py +1 -1
  52. teuthology/repo_utils.py +43 -13
  53. teuthology/report.py +57 -35
  54. teuthology/results.py +5 -3
  55. teuthology/run.py +13 -14
  56. teuthology/run_tasks.py +27 -43
  57. teuthology/schedule.py +4 -3
  58. teuthology/scrape.py +28 -22
  59. teuthology/suite/__init__.py +74 -45
  60. teuthology/suite/build_matrix.py +34 -24
  61. teuthology/suite/fragment-merge.lua +105 -0
  62. teuthology/suite/matrix.py +31 -2
  63. teuthology/suite/merge.py +175 -0
  64. teuthology/suite/placeholder.py +6 -9
  65. teuthology/suite/run.py +175 -100
  66. teuthology/suite/util.py +64 -218
  67. teuthology/task/__init__.py +1 -1
  68. teuthology/task/ansible.py +101 -32
  69. teuthology/task/buildpackages.py +2 -2
  70. teuthology/task/ceph_ansible.py +13 -6
  71. teuthology/task/cephmetrics.py +2 -1
  72. teuthology/task/clock.py +33 -14
  73. teuthology/task/exec.py +18 -0
  74. teuthology/task/hadoop.py +2 -2
  75. teuthology/task/install/__init__.py +29 -7
  76. teuthology/task/install/bin/adjust-ulimits +16 -0
  77. teuthology/task/install/bin/daemon-helper +114 -0
  78. teuthology/task/install/bin/stdin-killer +263 -0
  79. teuthology/task/install/deb.py +1 -1
  80. teuthology/task/install/rpm.py +17 -5
  81. teuthology/task/install/util.py +3 -3
  82. teuthology/task/internal/__init__.py +41 -10
  83. teuthology/task/internal/edit_sudoers.sh +10 -0
  84. teuthology/task/internal/lock_machines.py +2 -9
  85. teuthology/task/internal/redhat.py +31 -1
  86. teuthology/task/internal/syslog.py +31 -8
  87. teuthology/task/kernel.py +152 -145
  88. teuthology/task/lockfile.py +1 -1
  89. teuthology/task/mpi.py +10 -10
  90. teuthology/task/pcp.py +1 -1
  91. teuthology/task/selinux.py +16 -8
  92. teuthology/task/ssh_keys.py +4 -4
  93. teuthology/task/tests/__init__.py +137 -77
  94. teuthology/task/tests/test_fetch_coredumps.py +116 -0
  95. teuthology/task/tests/test_run.py +4 -4
  96. teuthology/timer.py +3 -3
  97. teuthology/util/loggerfile.py +19 -0
  98. teuthology/util/scanner.py +159 -0
  99. teuthology/util/sentry.py +52 -0
  100. teuthology/util/time.py +52 -0
  101. teuthology-1.2.0.data/scripts/adjust-ulimits +16 -0
  102. teuthology-1.2.0.data/scripts/daemon-helper +114 -0
  103. teuthology-1.2.0.data/scripts/stdin-killer +263 -0
  104. teuthology-1.2.0.dist-info/METADATA +89 -0
  105. teuthology-1.2.0.dist-info/RECORD +174 -0
  106. {teuthology-1.1.0.dist-info → teuthology-1.2.0.dist-info}/WHEEL +1 -1
  107. {teuthology-1.1.0.dist-info → teuthology-1.2.0.dist-info}/entry_points.txt +3 -2
  108. scripts/nuke.py +0 -47
  109. scripts/worker.py +0 -37
  110. teuthology/nuke/actions.py +0 -456
  111. teuthology/openstack/test/__init__.py +0 -0
  112. teuthology/openstack/test/openstack-integration.py +0 -286
  113. teuthology/openstack/test/test_config.py +0 -35
  114. teuthology/openstack/test/test_openstack.py +0 -1695
  115. teuthology/orchestra/test/__init__.py +0 -0
  116. teuthology/orchestra/test/integration/__init__.py +0 -0
  117. teuthology/orchestra/test/integration/test_integration.py +0 -94
  118. teuthology/orchestra/test/test_cluster.py +0 -240
  119. teuthology/orchestra/test/test_connection.py +0 -106
  120. teuthology/orchestra/test/test_console.py +0 -217
  121. teuthology/orchestra/test/test_opsys.py +0 -404
  122. teuthology/orchestra/test/test_remote.py +0 -185
  123. teuthology/orchestra/test/test_run.py +0 -286
  124. teuthology/orchestra/test/test_systemd.py +0 -54
  125. teuthology/orchestra/test/util.py +0 -12
  126. teuthology/test/__init__.py +0 -0
  127. teuthology/test/fake_archive.py +0 -107
  128. teuthology/test/fake_fs.py +0 -92
  129. teuthology/test/integration/__init__.py +0 -0
  130. teuthology/test/integration/test_suite.py +0 -86
  131. teuthology/test/task/__init__.py +0 -205
  132. teuthology/test/task/test_ansible.py +0 -624
  133. teuthology/test/task/test_ceph_ansible.py +0 -176
  134. teuthology/test/task/test_console_log.py +0 -88
  135. teuthology/test/task/test_install.py +0 -337
  136. teuthology/test/task/test_internal.py +0 -57
  137. teuthology/test/task/test_kernel.py +0 -243
  138. teuthology/test/task/test_pcp.py +0 -379
  139. teuthology/test/task/test_selinux.py +0 -35
  140. teuthology/test/test_config.py +0 -189
  141. teuthology/test/test_contextutil.py +0 -68
  142. teuthology/test/test_describe_tests.py +0 -316
  143. teuthology/test/test_email_sleep_before_teardown.py +0 -81
  144. teuthology/test/test_exit.py +0 -97
  145. teuthology/test/test_get_distro.py +0 -47
  146. teuthology/test/test_get_distro_version.py +0 -47
  147. teuthology/test/test_get_multi_machine_types.py +0 -27
  148. teuthology/test/test_job_status.py +0 -60
  149. teuthology/test/test_ls.py +0 -48
  150. teuthology/test/test_misc.py +0 -391
  151. teuthology/test/test_nuke.py +0 -290
  152. teuthology/test/test_packaging.py +0 -763
  153. teuthology/test/test_parallel.py +0 -28
  154. teuthology/test/test_repo_utils.py +0 -225
  155. teuthology/test/test_report.py +0 -77
  156. teuthology/test/test_results.py +0 -155
  157. teuthology/test/test_run.py +0 -239
  158. teuthology/test/test_safepath.py +0 -55
  159. teuthology/test/test_schedule.py +0 -45
  160. teuthology/test/test_scrape.py +0 -167
  161. teuthology/test/test_timer.py +0 -80
  162. teuthology/test/test_vps_os_vers_parameter_checking.py +0 -84
  163. teuthology/test/test_worker.py +0 -303
  164. teuthology/worker.py +0 -354
  165. teuthology-1.1.0.dist-info/METADATA +0 -76
  166. teuthology-1.1.0.dist-info/RECORD +0 -213
  167. {teuthology-1.1.0.dist-info → teuthology-1.2.0.dist-info}/LICENSE +0 -0
  168. {teuthology-1.1.0.dist-info → teuthology-1.2.0.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.warn(msg.format(yaml=config.yaml_path))
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
- if response.status_code == 200:
295
- return job_id
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
- # This call is wrapped in a try/except because of:
298
- # http://tracker.ceph.com/issues/8166
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
- if resp_json:
305
- msg = resp_json.get('message', '')
306
- else:
307
- msg = response.text
308
-
309
- if msg and msg.endswith('already exists'):
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
- return job_id
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
- elif seed is None:
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 = extra_info.copy()
492
- job_info.update(job_config)
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.warn('test(s) did not finish before timeout of %d seconds',
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', 'master')
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, fake_ctx):
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
- teuthology.config.config.ctx = fake_ctx
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, fake_ctx)
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
- import sentry_sdk
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
- manager.__enter__()
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
- if teuth_config.sentry_dsn:
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
- suppress = manager.__exit__(*exc_info)
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[opt]:
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'}.items():
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 six.ensure_str(out).split("\n")
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 or job.get_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
- job.get_last_tlog_line()).ratio()
204
+ last_tlog_line).ratio()
205
205
  return ratio > 0.5
206
- return True
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
- # The backtrace terminated, if we have a buffer return it
315
- if len(bt_lines):
316
- return ("".join(bt_lines)).strip(), assertion
317
- else:
318
- log.warning("Saw end of BT but not start")
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
- # Something wrong with our parsing, drop it
323
- log.warning("Ignoring malparsed backtrace: {0}".format(
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
- bt_lines.append(line)
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 = six.ensure_str(log_basename).split(".")[0]
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])