import time
import logging
import subprocess
+from collections import OrderedDict
from textwrap import dedent
from textwrap import fill
def build_email_body(name, _reporter=None):
- failed = {}
- dead = {}
- running = {}
- waiting = {}
- queued = {}
- passed = {}
+ stanzas = OrderedDict([
+ ('fail', dict()),
+ ('dead', dict()),
+ ('running', dict()),
+ ('waiting', dict()),
+ ('queued', dict()),
+ ('pass', dict()),
+ ])
reporter = _reporter or ResultsReporter()
fields = ('job_id', 'status', 'description', 'duration', 'failure_reason',
'sentry_event', 'log_href')
jobs.sort(key=lambda job: job['job_id'])
for job in jobs:
- job_id = job['job_id']
- status = job['status']
- description = job['description']
- duration = int(job['duration'] or 0)
-
- # Every job gets a link to e.g. pulpito's pages
- info_url = misc.get_results_url(name, job_id)
- if info_url:
- info_line = email_templates['info_url_templ'].format(info=info_url)
- else:
- info_line = ''
-
- if status in UNFINISHED_STATUSES:
- format_args = dict(
- job_id=job_id,
- desc=description,
- time=duration,
- info_line=info_line,
- )
- if status == 'running':
- running[job_id] = email_templates['running_templ'].format(
- **format_args)
- elif status == 'waiting':
- waiting[job_id] = email_templates['running_templ'].format(
- **format_args)
- elif status == 'queued':
- queued[job_id] = email_templates['running_templ'].format(
- **format_args)
- continue
-
- if status == 'pass':
- passed[job_id] = email_templates['pass_templ'].format(
- job_id=job_id,
- desc=description,
- time=duration,
- info_line=info_line,
- )
- else:
- log_dir_url = job['log_href'].rstrip('teuthology.yaml')
- if log_dir_url:
- log_line = email_templates['fail_log_templ'].format(
- log=log_dir_url)
- else:
- log_line = ''
- sentry_event = job.get('sentry_event')
- if sentry_event:
- sentry_line = email_templates['fail_sentry_templ'].format(
- sentry_event=sentry_event)
- else:
- sentry_line = ''
-
- if job['failure_reason']:
- # 'fill' is from the textwrap module and it collapses a given
- # string into multiple lines of a maximum width as specified.
- # We want 75 characters here so that when we indent by 4 on the
- # next line, we have 79-character exception paragraphs.
- reason = fill(job['failure_reason'] or '', 75)
- reason = \
- '\n'.join((' ') + line for line in reason.splitlines())
- reason_lines = email_templates['fail_reason_templ'].format(
- reason=reason)
- else:
- reason_lines = ''
-
- format_args = dict(
- job_id=job_id,
- desc=description,
- time=duration,
- info_line=info_line,
- log_line=log_line,
- sentry_line=sentry_line,
- reason_lines=reason_lines,
+ job_stanza = format_job(name, job)
+ stanzas[job['status']][job['job_id']] = job_stanza
+
+ sections = OrderedDict.fromkeys(stanzas.keys(), '')
+ subject_fragments = []
+ for status in sections.keys():
+ stanza = stanzas[status]
+ if stanza:
+ subject_fragments.append('%s %s' % (len(stanza), status))
+ sections[status] = email_templates['sect_templ'].format(
+ title=status.title(),
+ jobs=''.join(stanza.values()),
)
- if status == 'fail':
- failed[job_id] = email_templates['fail_templ'].format(
- **format_args)
- elif status == 'dead':
- dead[job_id] = email_templates['fail_templ'].format(
- **format_args)
-
- maybe_comma = lambda s: ', ' if s else ' '
-
- subject = ''
- fail_sect = ''
- dead_sect = ''
- running_sect = ''
- waiting_sect = ''
- queued_sect = ''
- pass_sect = ''
- if failed:
- subject += '{num_failed} failed{sep}'.format(
- num_failed=len(failed),
- sep=maybe_comma(dead or running or waiting or queued or passed)
- )
- fail_sect = email_templates['sect_templ'].format(
- title='Failed',
- jobs=''.join(failed.values())
- )
- if dead:
- subject += '{num_dead} dead{sep}'.format(
- num_dead=len(dead),
- sep=maybe_comma(running or waiting or queued or passed)
- )
- dead_sect = email_templates['sect_templ'].format(
- title='Dead',
- jobs=''.join(dead.values()),
- )
- if running:
- subject += '{num_running} running{sep}'.format(
- num_running=len(running),
- sep=maybe_comma(waiting or queued or passed)
- )
- running_sect = email_templates['sect_templ'].format(
- title='Running',
- jobs=''.join(running.values()),
- )
- if waiting:
- subject += '{num_waiting} waiting{sep}'.format(
- num_waiting=len(waiting),
- sep=maybe_comma(running or waiting or queued or passed)
- )
- waiting_sect = email_templates['sect_templ'].format(
- title='Waiting',
- jobs=''.join(waiting.values()),
- )
- if queued:
- subject += '{num_queued} queued{sep}'.format(
- num_queued=len(queued),
- sep=maybe_comma(running or waiting or queued or passed)
- )
- queued_sect = email_templates['sect_templ'].format(
- title='Queued',
- jobs=''.join(queued.values()),
- )
- if passed:
- subject += '%s passed ' % len(passed)
- pass_sect = email_templates['sect_templ'].format(
- title='Passed',
- jobs=''.join(passed.values()),
- )
+ subject = ', '.join(subject_fragments) + ' '
if config.archive_server:
log_root = os.path.join(config.archive_server, name, '')
name=name,
info_root=misc.get_results_url(name),
log_root=log_root,
- fail_count=len(failed),
- dead_count=len(dead),
- running_count=len(running),
- waiting_count=len(waiting),
- queued_count=len(queued),
- pass_count=len(passed),
- fail_sect=fail_sect,
- dead_sect=dead_sect,
- running_sect=running_sect,
- waiting_sect=waiting_sect,
- queued_sect=queued_sect,
- pass_sect=pass_sect,
+ fail_count=len(stanzas['fail']),
+ dead_count=len(stanzas['dead']),
+ running_count=len(stanzas['running']),
+ waiting_count=len(stanzas['waiting']),
+ queued_count=len(stanzas['queued']),
+ pass_count=len(stanzas['pass']),
+ fail_sect=sections['fail'],
+ dead_sect=sections['dead'],
+ running_sect=sections['running'],
+ waiting_sect=sections['waiting'],
+ queued_sect=sections['queued'],
+ pass_sect=sections['pass'],
)
subject += 'in {suite}'.format(suite=name)
return (subject.strip(), body.strip())
+
+def format_job(run_name, job):
+ job_id = job['job_id']
+ status = job['status']
+ description = job['description']
+ duration = int(job['duration'] or 0)
+
+ # Every job gets a link to e.g. pulpito's pages
+ info_url = misc.get_results_url(run_name, job_id)
+ if info_url:
+ info_line = email_templates['info_url_templ'].format(info=info_url)
+ else:
+ info_line = ''
+
+ if status in UNFINISHED_STATUSES:
+ format_args = dict(
+ job_id=job_id,
+ desc=description,
+ time=duration,
+ info_line=info_line,
+ )
+ return email_templates['running_templ'].format(**format_args)
+
+ if status == 'pass':
+ return email_templates['pass_templ'].format(
+ job_id=job_id,
+ desc=description,
+ time=duration,
+ info_line=info_line,
+ )
+ else:
+ log_dir_url = job['log_href'].rstrip('teuthology.yaml')
+ if log_dir_url:
+ log_line = email_templates['fail_log_templ'].format(
+ log=log_dir_url)
+ else:
+ log_line = ''
+ sentry_event = job.get('sentry_event')
+ if sentry_event:
+ sentry_line = email_templates['fail_sentry_templ'].format(
+ sentry_event=sentry_event)
+ else:
+ sentry_line = ''
+
+ if job['failure_reason']:
+ # 'fill' is from the textwrap module and it collapses a given
+ # string into multiple lines of a maximum width as specified.
+ # We want 75 characters here so that when we indent by 4 on the
+ # next line, we have 79-character exception paragraphs.
+ reason = fill(job['failure_reason'] or '', 75)
+ reason = \
+ '\n'.join((' ') + line for line in reason.splitlines())
+ reason_lines = email_templates['fail_reason_templ'].format(
+ reason=reason)
+ else:
+ reason_lines = ''
+
+ format_args = dict(
+ job_id=job_id,
+ desc=description,
+ time=duration,
+ info_line=info_line,
+ log_line=log_line,
+ sentry_line=sentry_line,
+ reason_lines=reason_lines,
+ )
+ return email_templates['fail_templ'].format(**format_args)
+
+
email_templates = {
'body_templ': dedent("""\
Test Run: {name}