Add support for emailing results via SMTP

Utilises the new reporter plugin architecture to add support for
emailing success/failure messages based on layout.yaml.

This will assist in testing new gates as currently after a job has
finished if no report is sent back to gerrit then only the workers
logs can be consulted to see if it was successful. This will allow
developers to see exactly what zuul will return if they turn on
gerrit reporting.

Change-Id: I47ac038bbdffb0a0c75f8e63ff6978fd4b4d0a52
This commit is contained in:
Joshua Hesketh 2013-08-19 17:32:01 +10:00
parent 1879cf721c
commit 5fea867c70
9 changed files with 231 additions and 3 deletions

View File

@ -29,4 +29,31 @@ Gerrit Configuration
~~~~~~~~~~~~~~~~~~~~
The configuration for posting back to gerrit is shared with the gerrit
trigger in zuul.conf. Please consult the gerrit trigger documentation.
trigger in zuul.conf as described in :ref:`zuulconf`.
SMTP
----
A simple email reporter is also available.
SMTP Configuration
~~~~~~~~~~~~~~~~~~
zuul.conf contains the smtp server and default to/from as describe
in :ref:`zuulconf`.
Each pipeline can overwrite the to or from address by providing
alternatives as arguments to the reporter. For example, ::
pipelines:
- name: post-merge
manager: IndependentPipelineManager
trigger:
- event: change-merged
success:
smtp:
to: you@example.com
failure:
smtp:
to: you@example.com
from: alternative@example.com

View File

@ -139,6 +139,23 @@ zuul
is included). Defaults to ``false``.
``job_name_in_report=true``
smtp
""""
**server**
SMTP server hostname or address to use.
``server=localhost``
**default_from**
Who the email should appear to be sent from when emailing the report.
This can be overridden by individual pipelines.
``default_from=zuul@example.com``
**default_to**
Who the report should be emailed to by default.
This can be overridden by individual pipelines.
``default_to=you@example.com``
layout.yaml
~~~~~~~~~~~

View File

@ -18,4 +18,10 @@ state_dir=/var/lib/zuul
git_dir=/var/lib/zuul/git
;git_user_email=zuul@example.com
;git_user_name=zuul
status_url=https://jenkins.example.com/zuul/status
status_url=https://jenkins.example.com/zuul/status
[smtp]
server=localhost
port=25
default_from=zuul@example.com
default_to=you@example.com

25
tests/fixtures/layout-smtp.yaml vendored Normal file
View File

@ -0,0 +1,25 @@
pipelines:
- name: check
manager: IndependentPipelineManager
trigger:
gerrit:
- event: patchset-created
start:
smtp:
to: you@example.com
success:
gerrit:
verified: 1
smtp:
to: alternative_me@example.com
from: zuul_from@example.com
failure:
gerrit:
verified: -1
projects:
- name: org/project
check:
- project-merge:
- project-test1
- project-test2

View File

@ -14,3 +14,9 @@ git_user_name=zuul
push_change_refs=true
url_pattern=http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}
job_name_in_report=true
[smtp]
server=localhost
port=25
default_from=zuul@example.com
default_to=you@example.com

View File

@ -45,6 +45,7 @@ import zuul.scheduler
import zuul.webapp
import zuul.launcher.gearman
import zuul.reporter.gerrit
import zuul.reporter.smtp
import zuul.trigger.gerrit
import zuul.trigger.timer
@ -682,6 +683,35 @@ class FakeGearmanServer(gear.Server):
self.log.debug("done releasing queued jobs %s (%s)" % (regex, qlen))
class FakeSMTP(object):
log = logging.getLogger('zuul.FakeSMTP')
messages = []
def __init__(self, server, port):
self.server = server
self.port = port
def sendmail(self, from_email, to_email, msg):
self.log.info("Sending email from %s, to %s, with msg %s" % (
from_email, to_email, msg))
headers = msg.split('\n\n', 1)[0]
body = msg.split('\n\n', 1)[1]
FakeSMTP.messages.append(dict(
from_email=from_email,
to_email=to_email,
msg=msg,
headers=headers,
body=body,
))
return True
def quit(self):
return True
class TestScheduler(testtools.TestCase):
log = logging.getLogger("zuul.test")
@ -765,6 +795,7 @@ class TestScheduler(testtools.TestCase):
self.launcher = zuul.launcher.gearman.Gearman(self.config, self.sched)
zuul.lib.gerrit.Gerrit = FakeGerrit
self.useFixture(fixtures.MonkeyPatch('smtplib.SMTP', FakeSMTP))
self.gerrit = FakeGerritTrigger(
self.upstream_root, self.config, self.sched)
@ -782,6 +813,11 @@ class TestScheduler(testtools.TestCase):
self.sched.registerReporter(
zuul.reporter.gerrit.Reporter(self.gerrit))
self.smtp_reporter = zuul.reporter.smtp.Reporter(
self.config.get('smtp', 'default_from'),
self.config.get('smtp', 'default_to'),
self.config.get('smtp', 'server'))
self.sched.registerReporter(self.smtp_reporter)
self.sched.start()
self.sched.reconfigure(self.config)
@ -2670,3 +2706,34 @@ class TestScheduler(testtools.TestCase):
status_jobs.add(job['name'])
self.assertIn('project-bitrot-stable-old', status_jobs)
self.assertIn('project-bitrot-stable-older', status_jobs)
def test_check_smtp_pool(self):
self.config.set('zuul', 'layout_config',
'tests/fixtures/layout-smtp.yaml')
self.sched.reconfigure(self.config)
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
self.waitUntilSettled()
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(len(FakeSMTP.messages), 2)
# A.messages only holds what FakeGerrit places in it. Thus we
# work on the knowledge of what the first message should be as
# it is only configured to go to SMTP.
self.assertEqual('zuul@example.com',
FakeSMTP.messages[0]['from_email'])
self.assertEqual(['you@example.com'],
FakeSMTP.messages[0]['to_email'])
self.assertEqual('Starting check jobs.',
FakeSMTP.messages[0]['body'])
self.assertEqual('zuul_from@example.com',
FakeSMTP.messages[1]['from_email'])
self.assertEqual(['alternative_me@example.com'],
FakeSMTP.messages[1]['to_email'])
self.assertEqual(A.messages[0],
FakeSMTP.messages[1]['body'])

View File

@ -166,6 +166,7 @@ class Server(object):
import zuul.scheduler
import zuul.launcher.gearman
import zuul.reporter.gerrit
import zuul.reporter.smtp
import zuul.trigger.gerrit
import zuul.trigger.timer
import zuul.webapp
@ -183,11 +184,22 @@ class Server(object):
timer = zuul.trigger.timer.Timer(self.config, self.sched)
webapp = zuul.webapp.WebApp(self.sched)
gerrit_reporter = zuul.reporter.gerrit.Reporter(gerrit)
smtp_reporter = zuul.reporter.smtp.Reporter(
self.config.get('smtp', 'default_from')
if self.config.has_option('smtp', 'default_from') else 'zuul',
self.config.get('smtp', 'default_to')
if self.config.has_option('smtp', 'default_to') else 'zuul',
self.config.get('smtp', 'server')
if self.config.has_option('smtp', 'server') else 'localhost',
self.config.get('smtp', 'port')
if self.config.has_option('smtp', 'port') else 25
)
self.sched.setLauncher(gearman)
self.sched.registerTrigger(gerrit)
self.sched.registerTrigger(timer)
self.sched.registerReporter(gerrit_reporter)
self.sched.registerReporter(smtp_reporter)
self.sched.start()
self.sched.reconfigure(self.config)

View File

@ -54,7 +54,8 @@ class LayoutSchema(object):
trigger = v.Required(v.Any({'gerrit': toList(gerrit_trigger)},
{'timer': toList(timer_trigger)}))
report_actions = {'gerrit': variable_dict}
report_actions = {'gerrit': variable_dict,
'smtp': variable_dict}
pipeline = {v.Required('name'): str,
v.Required('manager'): manager,

67
zuul/reporter/smtp.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright 2013 Rackspace Australia
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import smtplib
from email.mime.text import MIMEText
class Reporter(object):
"""Sends off reports to emails via SMTP."""
name = 'smtp'
log = logging.getLogger("zuul.reporter.smtp.Reporter")
def __init__(self, smtp_default_from, smtp_default_to,
smtp_server='localhost', smtp_port=25):
"""Set up the reporter.
Takes parameters for the smtp server.
"""
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.smtp_default_from = smtp_default_from
self.smtp_default_to = smtp_default_to
def report(self, change, message, params):
"""Send the compiled report message via smtp."""
self.log.debug("Report change %s, params %s, message: %s" %
(change, params, message))
# Create a text/plain email message
from_email = params['from']\
if 'from' in params else self.smtp_default_from
to_email = params['to']\
if 'to' in params else self.smtp_default_to
msg = MIMEText(message)
msg['Subject'] = "Report change %s" % change
msg['From'] = from_email
msg['To'] = to_email
try:
s = smtplib.SMTP(self.smtp_server, self.smtp_port)
s.sendmail(from_email, to_email.split(','), msg.as_string())
s.quit()
except:
return "Could not send email via SMTP"
return
def getSubmitAllowNeeds(self, params):
"""Get a list of code review labels that are allowed to be
"needed" in the submit records for a change, with respect
to this queue. In other words, the list of review labels
this reporter itself is likely to set before submitting.
"""
return []