summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrevor Bramwell <tbramwell@linuxfoundation.org>2018-11-07 22:05:28 +0000
committerGerrit Code Review <gerrit@opnfv.org>2018-11-07 22:05:28 +0000
commit674812a20f6804c287e1355ec8a1adf907798a94 (patch)
tree8bdfde258ce78b38fe15d84fefdc42b22cdf6441
parent027e8b3dfe84300b4c50ea6a39f8dc7e360f479d (diff)
parent81cfb043f06ab71da7c021a063f80f6df58305cc (diff)
Merge "Rewrite Notification subsystem"
-rw-r--r--dashboard/booking_communication_agent.py50
-rw-r--r--dashboard/dashboard_api/__init__.py8
-rw-r--r--dashboard/dashboard_api/api.py91
-rw-r--r--dashboard/dashboard_notification/__init__.py10
-rw-r--r--dashboard/dashboard_notification/notification.py120
-rw-r--r--dashboard/src/api/models.py12
-rw-r--r--dashboard/src/api/serializers/old_serializers.py7
-rw-r--r--dashboard/src/api/urls.py1
-rw-r--r--dashboard/src/api/views.py10
-rw-r--r--dashboard/src/dashboard/tasks.py9
-rw-r--r--dashboard/src/notifier/admin.py6
-rw-r--r--dashboard/src/notifier/dispatchers.py33
-rw-r--r--dashboard/src/notifier/manager.py181
-rw-r--r--dashboard/src/notifier/migrations/0002_auto_20181102_1631.py44
-rw-r--r--dashboard/src/notifier/models.py40
-rw-r--r--dashboard/src/notifier/views.py11
-rw-r--r--dashboard/src/pharos_dashboard/settings.py4
-rw-r--r--dashboard/src/templates/notifier/email_ended.txt21
-rw-r--r--dashboard/src/templates/notifier/email_fulfilled.txt17
-rw-r--r--dashboard/src/templates/notifier/end_booking.html36
-rw-r--r--dashboard/src/templates/notifier/inbox.html2
-rw-r--r--dashboard/src/templates/notifier/new_booking.html34
-rw-r--r--dashboard/src/workflow/models.py7
23 files changed, 291 insertions, 463 deletions
diff --git a/dashboard/booking_communication_agent.py b/dashboard/booking_communication_agent.py
deleted file mode 100644
index dc4ea4d..0000000
--- a/dashboard/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/dashboard_api/__init__.py b/dashboard/dashboard_api/__init__.py
deleted file mode 100644
index ce1acf3..0000000
--- a/dashboard/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/dashboard_api/api.py b/dashboard/dashboard_api/api.py
deleted file mode 100644
index d40e0aa..0000000
--- a/dashboard/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/dashboard_notification/__init__.py b/dashboard/dashboard_notification/__init__.py
deleted file mode 100644
index b5914ce..0000000
--- a/dashboard/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/dashboard_notification/notification.py b/dashboard/dashboard_notification/notification.py
deleted file mode 100644
index 6843c76..0000000
--- a/dashboard/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/dashboard/src/api/models.py b/dashboard/src/api/models.py
index 9afc89a..4237559 100644
--- a/dashboard/src/api/models.py
+++ b/dashboard/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/dashboard/src/api/serializers/old_serializers.py b/dashboard/src/api/serializers/old_serializers.py
index f50b90b..0944881 100644
--- a/dashboard/src/api/serializers/old_serializers.py
+++ b/dashboard/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/dashboard/src/api/urls.py b/dashboard/src/api/urls.py
index 94f8279..4b1fe40 100644
--- a/dashboard/src/api/urls.py
+++ b/dashboard/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/dashboard/src/api/views.py b/dashboard/src/api/views.py
index 072354f..cc3a668 100644
--- a/dashboard/src/api/views.py
+++ b/dashboard/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/dashboard/src/dashboard/tasks.py b/dashboard/src/dashboard/tasks.py
index 0f7af1c..d4b4189 100644
--- a/dashboard/src/dashboard/tasks.py
+++ b/dashboard/src/dashboard/tasks.py
@@ -13,18 +13,12 @@ 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):
for hostrelation in qs:
@@ -90,6 +84,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/dashboard/src/notifier/admin.py b/dashboard/src/notifier/admin.py
index d3e8be5..4a2984c 100644
--- a/dashboard/src/notifier/admin.py
+++ b/dashboard/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/dashboard/src/notifier/dispatchers.py b/dashboard/src/notifier/dispatchers.py
deleted file mode 100644
index 1b66b37..0000000
--- a/dashboard/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/dashboard/src/notifier/manager.py b/dashboard/src/notifier/manager.py
index a705d00..cc1aa16 100644
--- a/dashboard/src/notifier/manager.py
+++ b/dashboard/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", "<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 (<owner, collaborator>.username)
- * recipient full name (<owner, collaborator>.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", "<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/dashboard/src/notifier/migrations/0002_auto_20181102_1631.py b/dashboard/src/notifier/migrations/0002_auto_20181102_1631.py
new file mode 100644
index 0000000..e5fef89
--- /dev/null
+++ b/dashboard/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/dashboard/src/notifier/models.py b/dashboard/src/notifier/models.py
index ed0edeb..5e7c60e 100644
--- a/dashboard/src/notifier/models.py
+++ b/dashboard/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 "<h3>" + self.title + "</h3>" # TODO - template?
diff --git a/dashboard/src/notifier/views.py b/dashboard/src/notifier/views.py
index 026894a..c1a2f7e 100644
--- a/dashboard/src/notifier/views.py
+++ b/dashboard/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/dashboard/src/pharos_dashboard/settings.py b/dashboard/src/pharos_dashboard/settings.py
index 7fccb32..91fc93e 100644
--- a/dashboard/src/pharos_dashboard/settings.py
+++ b/dashboard/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/dashboard/src/templates/notifier/email_ended.txt b/dashboard/src/templates/notifier/email_ended.txt
new file mode 100644
index 0000000..7467a0e
--- /dev/null
+++ b/dashboard/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/dashboard/src/templates/notifier/email_fulfilled.txt b/dashboard/src/templates/notifier/email_fulfilled.txt
new file mode 100644
index 0000000..d473961
--- /dev/null
+++ b/dashboard/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/dashboard/src/templates/notifier/end_booking.html b/dashboard/src/templates/notifier/end_booking.html
new file mode 100644
index 0000000..22014fb
--- /dev/null
+++ b/dashboard/src/templates/notifier/end_booking.html
@@ -0,0 +1,36 @@
+<html>
+ <body>
+ <div id="message_content_wrapper">
+ {% if owner %}
+ <h3>Your booking has expired</h3>
+ <p>Your booking has ended and the machines have been cleaned up.</p>
+ <p>Thank you for working on OPNFV, and feel free to book more machines if you need them.</p>
+ {% else %}
+ <h3>A booking that you collaborated on has expired</h3>
+ <p>The booking owned by {{booking.owner.username}} that you worked on has ended</p>
+ <p>Thank you for contributing to OPNFV!</p>
+ {% endif %}
+ <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>
diff --git a/dashboard/src/templates/notifier/inbox.html b/dashboard/src/templates/notifier/inbox.html
index ee0f27a..c0ee1ba 100644
--- a/dashboard/src/templates/notifier/inbox.html
+++ b/dashboard/src/templates/notifier/inbox.html
@@ -65,7 +65,7 @@
</div>
<div class="iframe-panel inbox-expanded-view">
<div class="inbox-iframe-div">
- <iframe id="inbox-iframe" src="notification/0" frameBorder="0" width="100%" height="100vh" scrolling="yes" onload="sizetoiframe(this);">Please select a notification</iframe>
+ <iframe id="inbox-iframe" frameBorder="0" width="100%" height="100vh" scrolling="yes" onload="sizetoiframe(this);">Please select a notification</iframe>
</div>
</div>
</div>
diff --git a/dashboard/src/templates/notifier/new_booking.html b/dashboard/src/templates/notifier/new_booking.html
new file mode 100644
index 0000000..4b53875
--- /dev/null
+++ b/dashboard/src/templates/notifier/new_booking.html
@@ -0,0 +1,34 @@
+<html>
+ <body>
+ <div id="message_content_wrapper">
+ {% if owner %}
+ <h3>You have created a new booking</h3>
+ <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>
+ {% endif %}
+ <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>
diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py
index e862957..e5a23b2 100644
--- a/dashboard/src/workflow/models.py
+++ b/dashboard/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: