From: Parker Berberian Date: Wed, 24 Oct 2018 19:12:32 +0000 (-0400) Subject: Rewrite Notification subsystem X-Git-Tag: 2.0.99~190^2 X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=commitdiff_plain;h=7b15aed77c6675286fd75b8832af58c992717ef9;p=laas.git Rewrite Notification subsystem In this commit: - delete a lot of really bad and / or unused code - redesign a much simpler Notification model - create and send notifications to the user's inbox on booking start & end - migrations - emails user when booking is ready and when it ends Not in this commit: - Creating notifications from lab messages - warning messages when a booking is about to end - creating "summary" notifications when e.g. a booking has been fulfilled by a lab Change-Id: I69b4dc36c3f2bce76d810106baadeef5a562cc7d Signed-off-by: Parker Berberian --- diff --git a/booking_communication_agent.py b/booking_communication_agent.py deleted file mode 100644 index dc4ea4d..0000000 --- a/booking_communication_agent.py +++ /dev/null @@ -1,50 +0,0 @@ -############################################################################## -# Copyright (c) 2018 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 dashboard_notification.notification import Notification -from dashboard_api.api import DashboardAPI - -CONFIG = { - 'dashboard_ip': '127.0.0.1', - 'dashboard_url': 'http://127.0.0.1', - 'api_token': 'f33ff43c85ecb13f5d0632c05dbb0a7d85a5a8d1', - 'user': 'opnfv', - 'password': 'opnfvopnfv' -} - -api = DashboardAPI(CONFIG['dashboard_url'], api_token=CONFIG['api_token'], verbose=True) - - -def booking_start(message): - content = message.content - booking = api.get_booking(id=content['booking_id']) - - # do something here... - - # notify dashboard - api.post_resource_status(resource_id=booking['resource_id'], type='info', title='pod setup', - content='details') - - -def booking_end(message): - # do something here... - - # notify dashboard - api.post_resource_status(resource_id=message.content['resource_id'], type='info', - title='booking end', content='details') - - -def main(): - with Notification(CONFIG['dashboard_ip'], CONFIG['user'], CONFIG['password']) as notification: - notification.register(booking_start, 'Arm POD 2', 'booking_start') - notification.register(booking_end, 'Arm POD 2', 'booking_end') - notification.receive() # wait for notifications - - -if __name__ == "__main__": - main() diff --git a/dashboard_api/__init__.py b/dashboard_api/__init__.py deleted file mode 100644 index ce1acf3..0000000 --- a/dashboard_api/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## \ No newline at end of file diff --git a/dashboard_api/api.py b/dashboard_api/api.py deleted file mode 100644 index d40e0aa..0000000 --- a/dashboard_api/api.py +++ /dev/null @@ -1,91 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - -import logging - -import requests - -URLS = { - 'resources': '/api/resources/', - 'servers': '/api/servers/', - 'bookings': '/api/bookings', - 'resource_status': '/api/resource_status/', -} - -class DashboardAPI(object): - def __init__(self, dashboard_url, api_token='', verbose=False): - self._api_token = api_token - self._verbose = verbose - self._resources_url = dashboard_url + URLS['resources'] - self._servers_url = dashboard_url + URLS['servers'] - self._bookings_url = dashboard_url + URLS['bookings'] - self._resources_status_url = dashboard_url + URLS['resource_status'] - self._logger = logging.getLogger(__name__) - - def get_all_resources(self): - return self._get_json(self._resources_url) - - def get_resource(self, id='', name='', url=''): - if url != '': - return self._get_json(url)[0] - url = self._resources_url + self._url_parameter(id=id, name=name) - return self._get_json(url)[0] - - def get_all_bookings(self): - return self._get_json(self._bookings_url) - - def get_resource_bookings(self, resource_id): - url = self._bookings_url + self._url_parameter(resource_id=resource_id) - return self._get_json(url) - - def get_booking(self, id): - url = self._bookings_url + self._url_parameter(id=id) - return self._get_json(url)[0] - - def post_resource_status(self, resource_id, type, title, content): - data = { - 'resource': resource_id, - 'type': type, - 'title': title, - 'content': content - } - return self._post_json(self._resources_status_url, data) - - def get_url(self, url): - return self._get_json(url) - - def _url_parameter(self, **kwargs): - res = '' - prefix = '?' - for key, val in kwargs.items(): - res += prefix + key + '=' + str(val) - prefix = '&' - return res - - def _get_json(self, url): - try: - response = requests.get(url) - if self._verbose: - print('Get JSON: ' + url) - print(response.status_code, response.content) - return response.json() - except requests.exceptions.RequestException as e: - self._logger.exception(e) - except ValueError as e: - self._logger.exception(e) - - def _post_json(self, url, json): - if self._api_token == '': - raise Exception('Need api token to POST data.') - response = requests.post(url, json, headers={'Authorization': 'Token ' + self._api_token}) - if self._verbose: - print('Post JSON: ' + url) - print(response.status_code, response.content) - return response.status_code diff --git a/dashboard_notification/__init__.py b/dashboard_notification/__init__.py deleted file mode 100644 index b5914ce..0000000 --- a/dashboard_notification/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - - diff --git a/dashboard_notification/notification.py b/dashboard_notification/notification.py deleted file mode 100644 index 6843c76..0000000 --- a/dashboard_notification/notification.py +++ /dev/null @@ -1,120 +0,0 @@ -############################################################################## -# Copyright (c) 2016 Max Breitenfeldt 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 -############################################################################## - -import jsonpickle -import pika - - -class Message(object): - def __init__(self, type, topic, content): - self.type = type - self.topic = topic - self.content = content - - -class Notification(object): - """ - This class can be used by the dashboard and the labs to exchange notifications about booking - events and pod status. It utilizes rabbitmq to communicate. - - Notifications are associated to an event and to a topic. - Events are: - [ 'booking_start', 'booking_end'] - The topic is usually a POD name, ie: - 'Intel POD 2' - """ - - def __init__(self, dashboard_url, user=None, password=None, verbose=False): - self.rabbitmq_broker = dashboard_url - self.verbose = verbose - if user is None and password is None: - self._connection = pika.BlockingConnection(pika.ConnectionParameters( - host=self.rabbitmq_broker)) - else: - self.credentials = pika.PlainCredentials(user, password) - self._connection = pika.BlockingConnection(pika.ConnectionParameters( - credentials=self.credentials, - host=self.rabbitmq_broker)) - self._registry = {} - self._channel = self._connection.channel() - self._channel.exchange_declare(exchange='notifications', type='topic') - self._result = self._channel.queue_declare(exclusive=True, durable=True) - self._queue_name = self._result.method.queue - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self._connection.close() - - def register(self, function, topic, type='all'): - """ - Registers a function to be called for the specified event. - :param function: the function to register - :param event: the event type - :param regex: a regex to specify for wich topics the function will be called. Some - possible Expressions can be: - 'Intel POD 2' : Intel POD 2 - """ - - if topic not in self._registry: - self._registry[topic] = [(function, type)] - else: - self._registry[topic].append((function, type)) - - def receive(self): - """ - Start receiving notifications. This is a blocking operation, if a notification is received, - the registered functions will be called. - """ - if self.verbose: - print('Start receiving Notifications. Keys: ', self._registry.keys()) - self._receive_message(self._registry.keys()) - - def send(self, message): - """ - Send an event notification. - :param event: the event type - :param topic: the pod name - :param content: a JSON-serializable dictionary - """ - self._send_message(message) - - def _send_message(self, message): - routing_key = message.topic - message_json = jsonpickle.encode(message) - self._channel.basic_publish(exchange='notifications', - routing_key=routing_key, - body=message_json, - properties=pika.BasicProperties( - content_type='application/json', - delivery_mode=2, # make message persistent - )) - if self.verbose: - print(" [x] Sent %r:%r" % (routing_key, message_json)) - - def _receive_message(self, binding_keys): - for key in binding_keys: - self._channel.queue_bind(exchange='notifications', - queue=self._queue_name, - routing_key=key) - self._channel.basic_consume(self._message_callback, - queue=self._queue_name) - self._channel.start_consuming() - - def _message_callback(self, ch, method, properties, body): - if self.verbose: - print(" [x] Got %r:%r" % (method.routing_key, body)) - if method.routing_key not in self._registry: - return - for func, type in self._registry[method.routing_key]: - message = jsonpickle.decode(body.decode()) - if message.type == type: - func(message) - ch.basic_ack(delivery_tag=method.delivery_tag) diff --git a/src/api/models.py b/src/api/models.py index 7448ac4..9a7f775 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -217,6 +217,18 @@ class Job(models.Model): tasklist += list(cls.objects.filter(job=self).filter(status=status)) return tasklist + def is_fulfilled(self): + """ + This method should return true if all of the job's tasks are done, + and false otherwise + """ + my_tasks = self.get_tasklist() + for task in my_tasks: + if task.status != JobStatus.DONE: + return False + return True + + def get_delta(self, status): d = {} j = {} diff --git a/src/api/serializers/old_serializers.py b/src/api/serializers/old_serializers.py index f50b90b..0944881 100644 --- a/src/api/serializers/old_serializers.py +++ b/src/api/serializers/old_serializers.py @@ -11,13 +11,6 @@ from rest_framework import serializers from account.models import UserProfile -from notifier.models import Notifier - - -class NotifierSerializer(serializers.ModelSerializer): - class Meta: - model = Notifier - fields = ('id', 'title', 'content', 'user', 'sender', 'message_type', 'msg_sent') class UserSerializer(serializers.ModelSerializer): diff --git a/src/api/urls.py b/src/api/urls.py index 94f8279..4b1fe40 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -32,7 +32,6 @@ from api.views import * router = routers.DefaultRouter() router.register(r'bookings', BookingViewSet) -router.register(r'notifier', NotifierViewSet) router.register(r'user', UserViewSet) urlpatterns = [ diff --git a/src/api/views.py b/src/api/views.py index 072354f..cc3a668 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -21,11 +21,11 @@ from django.views.decorators.csrf import csrf_exempt import json from api.serializers.booking_serializer import * -from api.serializers.old_serializers import NotifierSerializer, UserSerializer +from api.serializers.old_serializers import UserSerializer from account.models import UserProfile from booking.models import Booking -from notifier.models import Notifier from api.models import * +from notifier.manager import NotificationHandler class BookingViewSet(viewsets.ModelViewSet): @@ -34,11 +34,6 @@ class BookingViewSet(viewsets.ModelViewSet): filter_fields = ('resource', 'id') -class NotifierViewSet(viewsets.ModelViewSet): - queryset = Notifier.objects.none() - serializer_class = NotifierSerializer - - class UserViewSet(viewsets.ModelViewSet): queryset = UserProfile.objects.all() serializer_class = UserSerializer @@ -87,6 +82,7 @@ def specific_task(request, lab_name="", job_id="", task_id=""): if 'message' in request.POST: task.message = request.POST.get('message') task.save() + NotificationHandler.task_updated(task) d = {} d['task'] = task.config.get_delta() m = {} diff --git a/src/dashboard/tasks.py b/src/dashboard/tasks.py index 48008b6..c619642 100644 --- a/src/dashboard/tasks.py +++ b/src/dashboard/tasks.py @@ -13,17 +13,11 @@ from celery import shared_task from django.utils import timezone from django.db.models import Q from booking.models import Booking -from notifier.manager import * -from notifier.models import * +from notifier.manager import NotificationHandler from api.models import * from resource_inventory.resource_manager import ResourceManager -@shared_task -def conjure_aggregate_notifiers(): - NotifyPeriodic.task() - - @shared_task def booking_poll(): def cleanup_hardware(qs): @@ -86,6 +80,7 @@ def booking_poll(): cleanup_access(AccessRelation.objects.filter(job=job)) job.complete = True job.save() + NotificationHandler.notify_booking_end(booking) @shared_task diff --git a/src/notifier/admin.py b/src/notifier/admin.py index d3e8be5..4a2984c 100644 --- a/src/notifier/admin.py +++ b/src/notifier/admin.py @@ -9,8 +9,6 @@ from django.contrib import admin -from notifier.models import * +from notifier.models import Notification -admin.site.register(Notifier) -admin.site.register(MetaBooking) -admin.site.register(LabMessage) +admin.site.register(Notification) diff --git a/src/notifier/dispatchers.py b/src/notifier/dispatchers.py deleted file mode 100644 index 1b66b37..0000000 --- a/src/notifier/dispatchers.py +++ /dev/null @@ -1,33 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 django.db.models.signals import pre_save -from django.dispatch import receiver -from django.contrib import messages -from django.core.mail import send_mail - -class DispatchHandler(): - - @receiver(pre_save, sender='notifier.Notifier') - def dispatch(sender, instance, *args, **kwargs): - try: - msg_type = getattr(DispatchHandler, instance.message_type) - msg_type(instance) - except AttributeError: - instance.msg_sent = 'no dispatcher by given name exists: sending by email' - email(instance) - - def email(instance): - if instance.msg_sent != 'no dispatcher by given name exists: sending by email': - instance.msg_sent = 'by email' - send_mail(instance.title,instance.content, - instance.sender,[instance.user.email_addr], fail_silently=False) - - def webnotification(instance): - instance.msg_sent='by web notification' diff --git a/src/notifier/manager.py b/src/notifier/manager.py index a705d00..cc1aa16 100644 --- a/src/notifier/manager.py +++ b/src/notifier/manager.py @@ -1,98 +1,125 @@ ############################################################################## -# Copyright (c) 2018 Sawyer Bergeron 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 ############################################################################## +import os +from notifier.models import Notification -from booking.models import * -from notifier.models import Notifier, MetaBooking, LabMessage -from django.utils import timezone -from datetime import timedelta -from django.template import Template, Context -from account.models import UserProfile +from django.core.mail import send_mail +from django.template.loader import render_to_string -from django.db import models -class NotifyPeriodic(object): - def task(): - bookings_new = Booking.objects.filter(metabooking__isnull=True) - bookings_old = Booking.objects.filter(end__lte=timezone.now() + timedelta(hours=24)).filter(metabooking__ended_notified=False) +class NotificationHandler(object): - for booking in bookings_old: - metabooking = booking.metabooking - if booking.end <= timezone.now() + timedelta(hours=24): - if not metabooking.ending_notified: - Notify().notify(Notify.TOCLEAN, booking) - metabooking.ending_notified = True - metabooking.save() - if booking.end <= timezone.now(): - metabooking = booking.metabooking - if not metabooking.ended_notified: - Notify().notify(Notify.CLEANED, booking) - metabooking.ended_notified = True - metabooking.save() + @classmethod + def notify_new_booking(cls, booking): + template = "notifier/new_booking.html" + titles = ["You have a new Booking", "You have been added to a Booking"] + cls.booking_notify(booking, template, titles) - for booking in bookings_new: - metabooking = MetaBooking() - metabooking.booking = booking - metabooking.created_notified = True - metabooking.save() + @classmethod + def notify_booking_end(cls, booking): + template = "notifier/end_booking.html" + titles = ["Your booking has ended", "A booking you collaborate on has ended"] + cls.booking_notify(booking, template, titles) - Notify().notify(Notify.CREATED, booking) + @classmethod + def booking_notify(cls, booking, template, titles): + """ + Creates a notification for a booking owner and collaborators + using the template. + titles is a list - the first is the title for the owner's notification, + the last is the title for the collaborators' + """ + owner_notif = Notification.objects.create( + title=titles[0], + content=render_to_string(template, context={ + "booking": booking, + "owner": True + }) + ) + owner_notif.recipients.add(booking.owner) + if not booking.collaborators.all().exists(): + return # no collaborators - were done + collab_notif = Notification.objects.create( + title=titles[-1], + content=render_to_string(template, context={ + "booking": booking, + "owner": False + }) + ) + for c in booking.collaborators.all(): + collab_notif.recipients.add(c) -class Notify(object): + @classmethod + def email_job_fulfilled(cls, job): + template_name = "notifier/email_fulfilled.txt" + all_tasks = job.get_tasklist() + users = list(job.booking.collaborators.all()) + users.append(job.booking.owner) + for user in users: + 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: + user_tasklist.append({ + "title": task.type_str + " Message: ", + "content": task.message + }) + # gather up all the other needed info + context = { + "user_name": user.userprofile.full_name, + "messages": user_tasklist, + "booking_url": os.environ.get("DASHBOARD_URL", "") + "/booking/detail/" + str(job.booking.id) + "/" + } - CREATED = "created" - TOCLEAN = "toclean" - CLEANED = "cleaned" + # render email template + message = render_to_string(template_name, context) - TITLES = {} - TITLES["created"] = "Your booking has been confirmed" - TITLES["toclean"] = "Your booking is ending soon" - TITLES["cleaned"] = "Your booking has ended" + # finally, send the email + send_mail( + "Your Booking is Ready", + message, + os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"), + user.userprofile.email_addr, + fail_silently=False + ) - """ - Lab message is provided with the following context elements: - * if is for owner or for collaborator (if owner) - * recipient username (.username) - * recipient full name (.userprofile.full_name) - * booking it pertains to (booking) - * status message should convey (currently "created", "toclean" and "cleaned" as strings) - It should be a django template that can be rendered with these context elements - and should generally use all of them in one way or another. - It should be applicable to email, the web based general view, and should be scalable for - all device formats across those mediums. - """ - def notify(self, notifier_type, booking): - template = Template(LabMessage.objects.filter(lab=booking.lab).first().msg) + @classmethod + def email_booking_over(cls, booking): + template_name = "notifier/email_ended.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", "") + "/booking/detail/" + str(booking.id) + "/" + } - context = {} - context["owner"] = booking.owner - context["notify_type"] = notifier_type - context["booking"] = booking - message = template.render(Context(context)) - notifier = Notifier() - notifier.title = self.TITLES[notifier_type] - notifier.content = message - notifier.user = booking.owner.userprofile - notifier.sender = str(booking.lab) - notifier.save() - notifier.send() + message = render_to_string(template_name, context) + send_mail( + "Your Booking has Expired", + message, + os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"), + user.userprofile.email_addr, + fail_silently=False + ) - context["owner"] = False - - for user in booking.collaborators.all(): - context["collaborator"] = user - message = template.render(Context(context)) - notifier = Notifier() - notifier.title = self.TITLES[notifier_type] - notifier.content = message - notifier.user = UserProfile.objects.get(user=user) - notifier.sender = str(booking.lab) - notifier.save() - notifier.send() + @classmethod + 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 + """ + if task.job.is_fulfilled(): + cls.email_job_fulfilled(task.job) diff --git a/src/notifier/migrations/0002_auto_20181102_1631.py b/src/notifier/migrations/0002_auto_20181102_1631.py new file mode 100644 index 0000000..e5fef89 --- /dev/null +++ b/src/notifier/migrations/0002_auto_20181102_1631.py @@ -0,0 +1,44 @@ +# Generated by Django 2.1 on 2018-11-02 16:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_publicnetwork'), + ('notifier', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=150)), + ('content', models.TextField()), + ('recipients', models.ManyToManyField(to='account.UserProfile')), + ], + ), + migrations.RemoveField( + model_name='labmessage', + name='lab', + ), + migrations.RemoveField( + model_name='metabooking', + name='booking', + ), + migrations.RemoveField( + model_name='notifier', + name='user', + ), + migrations.DeleteModel( + name='LabMessage', + ), + migrations.DeleteModel( + name='MetaBooking', + ), + migrations.DeleteModel( + name='Notifier', + ), + ] diff --git a/src/notifier/models.py b/src/notifier/models.py index ed0edeb..5e7c60e 100644 --- a/src/notifier/models.py +++ b/src/notifier/models.py @@ -8,44 +8,16 @@ ############################################################################## from django.db import models -from booking.models import Booking from account.models import UserProfile -from fernet_fields import EncryptedTextField -from account.models import Lab -class MetaBooking(models.Model): - id = models.AutoField(primary_key=True) - booking = models.OneToOneField(Booking, on_delete=models.CASCADE, related_name="metabooking") - ending_notified = models.BooleanField(default=False) - ended_notified = models.BooleanField(default=False) - created_notified = models.BooleanField(default=False) - - -class LabMessage(models.Model): - lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - msg = models.TextField() # django template should be put here - - -class Notifier(models.Model): - id = models.AutoField(primary_key=True) - title = models.CharField(max_length=240) - content = EncryptedTextField() - user = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True, blank=True) - sender = models.CharField(max_length=240, default='unknown') - message_type = models.CharField(max_length=240, default='email', choices=( - ('email', 'Email'), - ('webnotification', 'Web Notification'))) - msg_sent = '' +class Notification(models.Model): + title = models.CharField(max_length=150) + content = models.TextField() + recipients = models.ManyToManyField(UserProfile) def __str__(self): return self.title - """ - Implement for next PR: send Notifier by media agreed to by user - """ - def send(self): - pass - - def getEmail(self): - return self.user.email_addr + def to_preview_html(self): + return "

" + self.title + "

" # TODO - template? diff --git a/src/notifier/views.py b/src/notifier/views.py index 026894a..c1a2f7e 100644 --- a/src/notifier/views.py +++ b/src/notifier/views.py @@ -7,28 +7,27 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## -from notifier.models import * +from notifier.models import Notification from django.shortcuts import render + def InboxView(request): if request.user.is_authenticated: user = request.user else: return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - return render(request, "notifier/inbox.html", {'notifier_messages': Notifier.objects.filter(user=user.userprofile)}) + return render(request, "notifier/inbox.html", {'notifications': Notification.objects.filter(recipient=user.userprofile)}) def NotificationView(request, notification_id): - if notification_id == 0: - pass if request.user.is_authenticated: user = request.user else: return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) - notification = Notifier.objects.get(id=notification_id) - if not notification.user.user.username == user.username: + notification = Notification.objects.get(id=notification_id) + if user not in notification.recipients: return render(request, "dashboard/login.html", {'title': 'Access Denied'}) return render(request, "notifier/notification.html", {'notification': notification}) diff --git a/src/pharos_dashboard/settings.py b/src/pharos_dashboard/settings.py index 7fccb32..91fc93e 100644 --- a/src/pharos_dashboard/settings.py +++ b/src/pharos_dashboard/settings.py @@ -194,10 +194,6 @@ CELERYBEAT_SCHEDULE = { 'task': 'dashboard.tasks.free_hosts', 'schedule': timedelta(minutes=1) }, - 'conjure_notifiers': { - 'task': 'dashboard.tasks.conjure_aggregate_notifiers', - 'schedule': timedelta(seconds=30) - }, } # Notifier Settings diff --git a/src/templates/notifier/email_ended.txt b/src/templates/notifier/email_ended.txt new file mode 100644 index 0000000..7467a0e --- /dev/null +++ b/src/templates/notifier/email_ended.txt @@ -0,0 +1,21 @@ +{{user_name|default:"Developer"}}, + +The booking you requested of the OPNFV Lab as a Service has ended. + +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}} + +Feel free to create another booking with us! + +Thank you for contributing to the OPNFV platform! + + - The Lab-as-a-Service team diff --git a/src/templates/notifier/email_fulfilled.txt b/src/templates/notifier/email_fulfilled.txt new file mode 100644 index 0000000..d473961 --- /dev/null +++ b/src/templates/notifier/email_fulfilled.txt @@ -0,0 +1,17 @@ +{{user_name|default:"Developer"}}, + +The booking you requested of the OPNFV Lab as a Service has finished deploying and is ready for you to use. + +The lab that fulfilled your booking request has sent you the following messages: + {% for message in messages %} + {% message.title %} + {% message.content %} + -------------------- + {% endfor %} + +You may visit the following link for more information: +{{booking_url}} + +Thank you for contributing to the OPNFV platform! + + - The Lab-as-a-Service team diff --git a/src/templates/notifier/end_booking.html b/src/templates/notifier/end_booking.html new file mode 100644 index 0000000..22014fb --- /dev/null +++ b/src/templates/notifier/end_booking.html @@ -0,0 +1,36 @@ + + +
+ {% if owner %} +

Your booking has expired

+

Your booking has ended and the machines have been cleaned up.

+

Thank you for working on OPNFV, and feel free to book more machines if you need them.

+ {% else %} +

A booking that you collaborated on has expired

+

The booking owned by {{booking.owner.username}} that you worked on has ended

+

Thank you for contributing to OPNFV!

+ {% endif %} +

Booking information:

+
    +
  • owner: {{booking.owner.username}}
  • +
  • id: {{booking.id}}
  • +
  • lab: {{booking.resource.template.lab.lab_user.username}}
  • +
  • resource: {{booking.resource.template.name}}
  • +
  • start: {{booking.start}}
  • +
  • end: {{booking.end}}
  • +
  • purpose: {{booking.purpose}}
  • +
  • collaborators: +
      + {% for user in booking.collaborators.all %} +
    • user.username
    • + {% empty %} +
    • No collaborators
    • + {% endfor %} +
    +
  • +
+ +

You can find more detailed information Here

+
+ + diff --git a/src/templates/notifier/inbox.html b/src/templates/notifier/inbox.html index ee0f27a..c0ee1ba 100644 --- a/src/templates/notifier/inbox.html +++ b/src/templates/notifier/inbox.html @@ -65,7 +65,7 @@
- +
diff --git a/src/templates/notifier/new_booking.html b/src/templates/notifier/new_booking.html new file mode 100644 index 0000000..4b53875 --- /dev/null +++ b/src/templates/notifier/new_booking.html @@ -0,0 +1,34 @@ + + +
+ {% if owner %} +

You have created a new booking

+

We have recieved your booking request and will start working on it right away.

+ {% else %} +

You have been added as a collaborator to a booking

+

{{booking.owner.username}} has given you access to thier booking.

+ {% endif %} +

Booking information:

+
    +
  • owner: {{booking.owner.username}}
  • +
  • id: {{booking.id}}
  • +
  • lab: {{booking.resource.template.lab.lab_user.username}}
  • +
  • resource: {{booking.resource.template.name}}
  • +
  • start: {{booking.start}}
  • +
  • end: {{booking.end}}
  • +
  • purpose: {{booking.purpose}}
  • +
  • collaborators: +
      + {% for user in booking.collaborators.all %} +
    • user.username
    • + {% empty %} +
    • No collaborators
    • + {% endfor %} +
    +
  • +
+ +

You can find more detailed information Here

+
+ + diff --git a/src/workflow/models.py b/src/workflow/models.py index e862957..e5a23b2 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -14,8 +14,6 @@ from django.shortcuts import render from django.contrib import messages import yaml -import json -import traceback import requests from workflow.forms import ConfirmationForm @@ -23,6 +21,7 @@ from api.models import * from dashboard.exceptions import * from resource_inventory.models import * from resource_inventory.resource_manager import ResourceManager +from notifier.manager import NotificationHandler class BookingAuthManager(): @@ -282,6 +281,9 @@ class Repository(): errors = self.make_booking() if errors: return errors + # create notification + booking = self.el[self.BOOKING_MODELS]['booking'] + NotificationHandler.notify_new_booking(booking) def make_snapshot(self): @@ -465,7 +467,6 @@ class Repository(): for collaborator in collaborators: booking.collaborators.add(collaborator) - try: JobFactory.makeCompleteJob(booking) except Exception as e: