Add warning email and notification 08/69108/3
authorBrandon Lo <lobrandon1217@gmail.com>
Tue, 26 Nov 2019 21:39:26 +0000 (16:39 -0500)
committerBrandon Lo <lobrandon1217@gmail.com>
Tue, 3 Dec 2019 20:16:16 +0000 (15:16 -0500)
This adds the abandoned changes made to the notification
system and also adds a simple task to check for expiring
bookings and sends out emails and notifications.

Change-Id: I1530d19f41cf93626bb642e6b269f9ec55860b81
Signed-off-by: Brandon Lo <lobrandon1217@gmail.com>
src/laas_dashboard/settings.py
src/notifier/admin.py
src/notifier/manager.py
src/notifier/migrations/0006_emailed.py [new file with mode: 0644]
src/notifier/models.py
src/notifier/tasks.py [new file with mode: 0644]
src/templates/notifier/email_ended.txt
src/templates/notifier/email_expiring.txt [new file with mode: 0644]
src/templates/notifier/email_fulfilled.txt
src/templates/notifier/expiring_booking.html [new file with mode: 0644]
src/templates/notifier/new_booking.html

index b73e2c8..951ce1a 100644 (file)
@@ -192,6 +192,10 @@ CELERYBEAT_SCHEDULE = {
         'task': 'dashboard.tasks.free_hosts',
         'schedule': timedelta(minutes=1)
     },
+    'notify_expiring': {
+        'task': 'notifier.tasks.notify_expiring',
+        'schedule': timedelta(hours=1)
+    },
 }
 
 # Notifier Settings
@@ -202,3 +206,5 @@ EMAIL_HOST_PASSWORD = os.environ['EMAIL_HOST_PASSWORD']
 EMAIL_USE_TLS = True
 DEFAULT_EMAIL_FROM = os.environ.get('DEFAULT_EMAIL_FROM', 'webmaster@localhost')
 SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
+EXPIRE_LIFETIME = 12 # Minimum lifetime of booking to send notification
+EXPIRE_HOURS = 48 # Notify when booking is expiring within this many hours
index 4a2984c..f6dbfd1 100644 (file)
@@ -9,6 +9,7 @@
 
 from django.contrib import admin
 
-from notifier.models import Notification
+from notifier.models import Notification, Emailed
 
 admin.site.register(Notification)
+admin.site.register(Emailed)
index 9b89ef5..ee849a8 100644 (file)
@@ -7,10 +7,11 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 import os
-from notifier.models import Notification
+from notifier.models import Notification, Emailed
 
 from django.core.mail import send_mail
 from django.template.loader import render_to_string
+from django.utils import timezone
 
 
 class NotificationHandler(object):
@@ -27,6 +28,13 @@ class NotificationHandler(object):
         titles = ["Your booking (" + str(booking.id) + ") has ended", "A booking (" + str(booking.id) + ") that you collaborate on has ended"]
         cls.booking_notify(booking, template, titles)
 
+    @classmethod
+    def notify_booking_expiring(cls, booking):
+        template = "notifier/expiring_booking.html"
+        titles = ["Your booking (" + str(booking.id) + ") is about to expire", "A booking (" + str(booking.id) + ") that you collaborate on is about to expire"]
+        cls.booking_notify(booking, template, titles)
+        cls.email_booking_expiring(booking)
+
     @classmethod
     def booking_notify(cls, booking, template, titles):
         """
@@ -72,7 +80,7 @@ class NotificationHandler(object):
             user_tasklist = []
             # gather up all the relevant messages from the lab
             for task in all_tasks:
-                if (not hasattr(task, "user")) or task.user == user:
+                if (not hasattr(task.config, "user")) or task.config.user == user:
                     user_tasklist.append(
                         {
                             "title": task.type_str() + " Message: ",
@@ -81,6 +89,7 @@ class NotificationHandler(object):
                     )
             # gather up all the other needed info
             context = {
+                "owner": user == job.booking.owner,
                 "user_name": user.userprofile.full_name,
                 "messages": user_tasklist,
                 "booking_url": os.environ.get("DASHBOARD_URL", "<Dashboard url>") + "/booking/detail/" + str(job.booking.id) + "/"
@@ -118,7 +127,31 @@ class NotificationHandler(object):
                 "Your Booking has Expired",
                 message,
                 os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@laas-dashboard"),
-                user.userprofile.email_addr,
+                [user.userprofile.email_addr],
+                fail_silently=False
+            )
+
+    @classmethod
+    def email_booking_expiring(cls, booking):
+        template_name = "notifier/email_expiring.txt"
+        hostnames = [host.template.resource.name for host in booking.resource.hosts.all()]
+        users = list(booking.collaborators.all())
+        users.append(booking.owner)
+        for user in users:
+            context = {
+                "user_name": user.userprofile.full_name,
+                "booking": booking,
+                "hosts": hostnames,
+                "booking_url": os.environ.get("DASHBOARD_URL", "<Dashboard url>") + "/booking/detail/" + str(booking.id) + "/"
+            }
+
+            message = render_to_string(template_name, context)
+
+            send_mail(
+                "Your Booking is Expiring",
+                message,
+                os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@laas-dashboard"),
+                [user.userprofile.email_addr],
                 fail_silently=False
             )
 
@@ -126,8 +159,20 @@ class NotificationHandler(object):
     def task_updated(cls, task):
         """
         called every time a lab updated info about a task.
-        currently only checks if the job is now done so I can send an email,
-        may add more functionality later
+        sends an email when 'task' changing state means a booking has
+        just been fulfilled (all tasks done, servers ready to use)
+        or is over.
         """
+        if task.job is None or task.job.booking is None:
+            return
         if task.job.is_fulfilled():
-            cls.email_job_fulfilled(task.job)
+            if task.job.booking.end < timezone.now():
+                if Emailed.objects.filter(end_booking=task.job.booking).exists():
+                    return
+                Emailed.objects.create(end_booking=task.job.booking)
+                cls.email_booking_over(task.job.booking)
+            if task.job.booking.end > timezone.now() and task.job.booking.start < timezone.now():
+                if Emailed.objects.filter(begin_booking=task.job.booking).exists():
+                    return
+                Emailed.objects.create(begin_booking=task.job.booking)
+                cls.email_job_fulfilled(task.job)
diff --git a/src/notifier/migrations/0006_emailed.py b/src/notifier/migrations/0006_emailed.py
new file mode 100644 (file)
index 0000000..22ba9c5
--- /dev/null
@@ -0,0 +1,24 @@
+# Generated by Django 2.2 on 2019-11-21 18:55
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('booking', '0006_booking_opnfv_config'),
+        ('notifier', '0005_auto_20190306_1616'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Emailed',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('almost_end_booking', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='warning_mail', to='booking.Booking')),
+                ('begin_booking', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='begin_mail', to='booking.Booking')),
+                ('end_booking', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='over_mail', to='booking.Booking')),
+            ],
+        ),
+    ]
index 49189e8..382d3a9 100644 (file)
@@ -9,6 +9,7 @@
 
 from django.db import models
 from account.models import UserProfile
+from booking.models import Booking
 
 
 class Notification(models.Model):
@@ -23,3 +24,29 @@ class Notification(models.Model):
 
     def to_preview_html(self):
         return "<h3>" + self.title + "</h3>"  # TODO - template?
+
+
+class Emailed(models.Model):
+    """
+    A simple record to remember who has already gotten an email
+    to avoid resending
+    """
+    begin_booking = models.OneToOneField(
+        Booking,
+        null=True,
+        on_delete=models.CASCADE,
+        related_name="begin_mail"
+    )
+    almost_end_booking = models.OneToOneField(
+        Booking,
+        null=True,
+        on_delete=models.CASCADE,
+        related_name="warning_mail"
+    )
+    end_booking = models.OneToOneField(
+        Booking,
+        null=True,
+        on_delete=models.CASCADE,
+        related_name="over_mail"
+    )
+
diff --git a/src/notifier/tasks.py b/src/notifier/tasks.py
new file mode 100644 (file)
index 0000000..b45ab8e
--- /dev/null
@@ -0,0 +1,35 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from celery import shared_task
+from django.utils import timezone
+from django.conf import settings
+from booking.models import Booking
+from notifier.models import Emailed
+from notifier.manager import NotificationHandler
+
+
+@shared_task
+def notify_expiring():
+    """
+    Notify users if their booking is within 48 hours of expiring.
+    """
+    expire_time = timezone.now() + timezone.timedelta(hours=settings.EXPIRE_HOURS)
+    # Don't email people about bookings that have started recently
+    start_time = timezone.now() - timezone.timedelta(hours=settings.EXPIRE_LIFETIME)
+    bookings = Booking.objects.filter(end__lte=expire_time,
+        end__gte=timezone.now(),
+        start__lte=start_time)
+    for booking in bookings:
+        if Emailed.objects.filter(almost_end_booking=booking).exists():
+            continue
+        NotificationHandler.notify_booking_expiring(booking)
+        Emailed.objects.create(almost_end_booking=booking)
index 7467a0e..1788e00 100644 (file)
@@ -1,6 +1,10 @@
 {{user_name|default:"Developer"}},
 
-The booking you requested of the OPNFV Lab as a Service has ended.
+{% if owner %}
+The booking you requested from the OPNFV Lab as a Service has ended.
+{% else %}
+The booking you collaborated on at the OPNFV Lab as a Service has ended.
+{% endif %}
 
 booking information:
     start: {{booking.start}}
diff --git a/src/templates/notifier/email_expiring.txt b/src/templates/notifier/email_expiring.txt
new file mode 100644 (file)
index 0000000..72c9a83
--- /dev/null
@@ -0,0 +1,25 @@
+{{user_name|default:"Developer"}},
+
+{% if owner %}
+The booking you requested from the OPNFV Lab as a Service is about to expire.
+{% else %}
+The booking you collaborate on at the OPNFV Lab as a Service is about to expire.
+{% endif %}
+
+booking information:
+    start: {{booking.start}}
+    end: {{booking.end}}
+    machines:
+        {% for host in hosts %}
+        - {{host}}
+        {% endfor %}
+    purpose: {{booking.purpose}}
+
+You may visit the following link for more information:
+{{booking_url}}
+
+Please take the time to backup all data or extend the booking if needed.
+
+Thank you for contributing to the OPNFV platform!
+
+    - The Lab-as-a-Service team
index 65593db..90d294a 100644 (file)
@@ -1,6 +1,10 @@
 {{user_name|default:"Developer"}},
 
+{% if owner %}
 The booking you requested of the OPNFV Lab as a Service has finished deploying and is ready for you to use.
+{% else %}
+A booking you collaborate on is ready for you to use
+{% endif %}
 
 The lab that fulfilled your booking request has sent you the following messages:
     {% for email_message in messages %}
diff --git a/src/templates/notifier/expiring_booking.html b/src/templates/notifier/expiring_booking.html
new file mode 100644 (file)
index 0000000..8bfa689
--- /dev/null
@@ -0,0 +1,35 @@
+<html>
+    <body>
+        <div id="message_content_wrapper">
+            {% if owner %}
+            <h3>Your booking is about to expire</h3>
+            <p>Your booking will expire within 48 hours ({{booking.end}}).</p>
+            {% else %}
+            <h3>A booking that you collaborate on is about to expire</h3>
+            <p>The booking owned by {{booking.owner.username}} that you work on is about to expire</p>
+            {% endif %}
+            <p>Please take the time to backup all data or extend the booking if needed.</p>
+            <p>Booking information:</p>
+            <ul>
+                <li>owner: {{booking.owner.username}}</li>
+                <li>id: {{booking.id}}</li>
+                <li>lab: {{booking.resource.template.lab.lab_user.username}}</li>
+                <li>resource: {{booking.resource.template.name}}</li>
+                <li>start: {{booking.start}}</li>
+                <li>end: {{booking.end}}</li>
+                <li>purpose: {{booking.purpose}}</li>
+                <li>collaborators:
+                    <ul>
+                        {% for user in booking.collaborators.all %}
+                        <li>user.username</li>
+                        {% empty %}
+                        <li>No collaborators</li>
+                        {% endfor %}
+                    </ul>
+                </li>
+            </ul>
+
+            <p>You can find more detailed information <a href=/booking/detail/{{booking.id}}/>Here</a></p>
+        </div>
+    </body>
+</html>
index d23b12e..64244e0 100644 (file)
@@ -6,7 +6,7 @@
             <p>We have recieved your booking request and will start working on it right away.</p>
             {% else %}
             <h3>You have been added as a collaborator to a booking</h3>
-            <p>{{booking.owner.username}} has given you access to thier booking.</p>
+            <p>{{booking.owner.username}} has given you access to their booking.</p>
             {% endif %}
             <p>Booking information:</p>
             <ul>