"""Alarm per Mail, wenn wegen Platzmangels ein Snapshot ausfaellt.""" from __future__ import annotations import logging import smtplib import socket import ssl from email.message import EmailMessage from email.utils import formatdate, make_msgid log = logging.getLogger("pvesnap.mail") SMTP_TIMEOUT = 30 class MailError(Exception): """Die Mail konnte nicht zugestellt werden.""" def _tls_context(mail): context = ssl.create_default_context() if not mail.tls_verify: context.check_hostname = False context.verify_mode = ssl.CERT_NONE return context def _connect(mail): if mail.security == "ssl": return smtplib.SMTP_SSL(mail.server, mail.effective_port, timeout=SMTP_TIMEOUT, context=_tls_context(mail)) client = smtplib.SMTP(mail.server, mail.effective_port, timeout=SMTP_TIMEOUT) if mail.security == "starttls": try: # Bietet der Server kein STARTTLS an, schlaegt das fehl. Weiter im # Klartext geht es dann bewusst nicht. client.starttls(context=_tls_context(mail)) except Exception: client.close() raise return client def send_mail(mail, subject, body): """Verschickt eine Mail; jedes Scheitern wird zu einem MailError.""" message = EmailMessage() message["Subject"] = subject message["From"] = mail.sender message["To"] = ", ".join(mail.recipients) message["Date"] = formatdate(localtime=True) message["Message-ID"] = make_msgid(domain=socket.getfqdn()) message.set_content(body) try: with _connect(mail) as client: if mail.user: client.login(mail.user, mail.password) client.send_message(message) except (OSError, smtplib.SMTPException, ValueError) as exc: raise MailError("%s: %s" % (mail.server, exc)) def send_test_mail(mail): host = socket.gethostname() send_mail(mail, "[pvesnap] %s: Test-Mail" % host, "Der Mail-Alarm von pvesnap auf %s funktioniert.\n" % host) def send_space_alert(config, group_name, messages): """Meldet die ausgefallenen Snapshots eines Laufs in einer einzigen Mail. Scheitert das, bleibt es bei einem Fehler im Protokoll: Eine kaputte Mail darf die uebrigen Gruppen nicht aufhalten. """ mail = config.mail if not mail.enabled: return host = socket.gethostname() if config.globals.dry_run: log.info("[TESTLAUF] wuerde Alarm-Mail an %s senden", ", ".join(mail.recipients)) return subject = ("[pvesnap] %s: %d Snapshot(s) wegen Platzmangels ausgefallen" % (host, len(messages))) body = ("Auf %s konnte pvesnap in der Gruppe '%s' nicht genug Platz schaffen.\n" "Fuer diese Gaeste wurde kein Snapshot angelegt:\n\n%s\n\n" "Der Speicher wird knapp - laeuft er voll, stehen alle Maschinen darauf,\n" "und auch das Aufraeumen kann dann scheitern. Bitte zeitnah pruefen:\n\n" " ceph df (Ceph: MAX AVAIL)\n" " pvesm status\n" " pvesnap list -a (auch von Hand angelegte Snapshots)\n" % (host, group_name, "\n".join(" - %s" % text for text in messages))) try: send_mail(mail, subject, body) log.info("Alarm-Mail an %s gesendet", ", ".join(mail.recipients)) except MailError as exc: log.error("Alarm-Mail nicht zustellbar: %s", exc)