mails.py 4.07 KB
Newer Older
1
import smtplib
2
import logging
3
from email.mime.text import MIMEText
4
from email.utils import make_msgid
5 6
from collections import defaultdict
from sys import stderr
7
from time import strftime
8
from datetime import datetime, timedelta
9
import email.charset
10 11
from threading import Thread
import queue
12
import atexit
13 14 15 16 17

# Switch to quoted-printable so that we don't get something completely
# unreadable for non-ASCII chars if we have to look at raw email
email.charset.add_charset('utf-8', email.charset.QP, email.charset.QP, 'utf-8')

Colomban Wendling's avatar
Colomban Wendling committed
18

19 20 21 22 23 24 25
class ThreadedSMTP(object):
    """A helper class managing a thread sending emails through smtplib"""

    def __init__(self):
        self._queue = queue.Queue()
        self._loop = True
        self._thread = Thread(target=self.__loop)
26
        self._thread.daemon = True
27
        self._thread.start()
28 29
        # properly clean up on quit
        atexit.register(self.quit)
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

    def quit(self):
        if self._loop:
            self._loop = False
            self._queue.put(((), {}))  # put a dummy item to wake up the thread
            self._queue.join()
            self._thread.join()

    def __server_quit(self, server=None):
        if server is not None:
            server.quit()
        return None

    def __loop(self):
        from . import config

        server = None
        while self._loop or not self._queue.empty():
            try:
49
                timeout = config.emails.smtp_keepalive_timeout
50 51 52 53 54 55 56
                args, kwargs = self._queue.get(timeout=timeout)
            except queue.Empty:
                server = self.__server_quit(server)
            else:
                if len(args) or len(kwargs):  # ignore empty items
                    try:
                        if server is None:
57
                            server = smtplib.SMTP(config.emails.smtp_host)
58 59
                        server.sendmail(*args, **kwargs)
                    except Exception as e:
60
                        logging.warning("Couldn't send email: %s" % str(e))
61
                self._queue.task_done()
62 63 64 65 66 67 68 69 70 71 72 73
        self.__server_quit(server)

    def sendmail(self, *args, **kwargs):
        self._queue.put((args, kwargs))


_mailer = ThreadedSMTP()


def quit():
    _mailer.quit()

74 75

def send_email(subject, body, extra_headers={}):
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    from . import config

    # encode / decode is a fix that didn't make it into Debian Wheezy
    # http://bugs.python.org/issue16948
    msg = MIMEText(body.encode('utf-8').decode('latin1'), 'plain', 'utf-8')

    msg['Subject'] = subject
    msg['From']    = config.emails.addr_from
    msg['To']      = ", ".join(config.emails.to)
    msg['Date']    = strftime('%a, %d %b %Y %H:%M:%S %z')

    for (key, val) in extra_headers.items():
        msg[key] = val

    _mailer.sendmail(config.emails.addr_from, config.emails.to,
                     msg.as_string())
92

93

94
def send_email_for_check(check):
95
    from . import config
96
    # ensure we do not traceback with unknown substitutions
97
    subject = config.emails.subject_tpl.format_map(
Colomban Wendling's avatar
Colomban Wendling committed
98 99 100 101
        defaultdict(lambda: "<no substitution>",
                    state='OK' if check.ok else 'Problem',
                    check=check.__class__.__name__,
                    dest=check.target_name))
102

103 104 105 106 107
    msg_text = "Check %s:\n" % str(check)
    if check.ok:
        delta = datetime.now() - check.failure_date
        # remove microsec
        delta = delta - timedelta(microseconds=delta.microseconds)
108
        n = check.retry_count + 1 - check.retry
109 110 111 112
        msg_text += ("recovered after %s (%d %s)." %
                     (delta, n, "retry" if n == 1 else "retries"))
    else:
        msg_text += ("failure:\n%s" % check.errmsg.strip())
113

114
    extra_headers = {}
115
    extra_headers['Message-ID'] = make_msgid(type(check).__name__)
116 117
    # if check is OK it's a follow up, so set In-Reply-To
    if check.ok and hasattr(check, 'mails_msgid'):
118 119 120
        extra_headers['In-Reply-To'] = check.mails_msgid
        extra_headers['References'] = check.mails_msgid
    check.mails_msgid = extra_headers['Message-ID']
121

122 123
    send_email(subject, msg_text, extra_headers)

124

Jonathan Michalon's avatar
Jonathan Michalon committed
125 126
def send_email_report(text):
    from . import config
127
    send_email(config.emails.report.subject, text)