From 17aa6b844dea5d202649d13ba4bc87e6bbb70aa6 Mon Sep 17 00:00:00 2001
From: Branson Stephens <stephenb@uwm.edu>
Date: Fri, 3 May 2013 16:38:32 -0500
Subject: [PATCH] added some basic error handling for log entries that fail to
 save because of IntegrityError

---
 gracedb/api.py    | 27 +++++++++++++++++++++---
 gracedb/models.py | 29 +++++++++++++++++++------
 gracedb/views.py  | 54 +++++++++++++++++++++++++++++++++++++----------
 3 files changed, 89 insertions(+), 21 deletions(-)

diff --git a/gracedb/api.py b/gracedb/api.py
index f2722cb60..364996cb5 100644
--- a/gracedb/api.py
+++ b/gracedb/api.py
@@ -421,6 +421,12 @@ class EventDetail(APIView):
             handle_uploaded_data(event, uploadDestination)
             event.submitter = request.ligouser
         except:
+            # XXX Bad news.  If the log file fails to save because of
+            # race conditions, then this will also be the the message
+            # returned.  Somehow, I think there are other things that
+            # could go wrong inside handle_uploaded_data besides just
+            # bad data.  We should probably check for different types
+            # of exceptions here.
             return Response("Bad Data",
                     status=status.HTTP_400_BAD_REQUEST)
         return Response(status=status.HTTP_202_ACCEPTED)
@@ -593,7 +599,12 @@ class EventLogList(APIView):
                 comment=message)
         logset = event.eventlog_set.order_by("created")
         n = len(logset)
-        logentry.save()
+        try:
+            logentry.save()
+        except Exception as e:
+            # Since this is likely due to race conditions, we will return 503
+            return Response("Failed to save log entry: %s" % str(e),
+                    status=status.HTTP_503_SERVICE_UNAVAILABLE)
         rv = eventLogToDict(logentry, n, request=request)
         response = Response(rv, status=status.HTTP_201_CREATED)
         response['Location'] = rv['self']
@@ -843,7 +854,12 @@ class EventLogTagDetail(APIView):
             logentry = EventLog(event=event,
                                issuer=request.ligouser,
                                comment=msg)
-            logentry.save()
+            try:    
+                logentry.save()
+            except Exception as e:
+                # Since the tag creation was successful, we'll return 201.
+                return Response("Tag succesful, but failed to create log entry: %s" % str(e),
+                     status=status.HTTP_201_CREATED)
 
             return Response("Tag created.",status=status.HTTP_201_CREATED)
 
@@ -872,7 +888,12 @@ class EventLogTagDetail(APIView):
             logentry = EventLog(event=event,
                                issuer=request.ligouser,
                                comment=msg)
-            logentry.save()
+            try:    
+                logentry.save()
+            except Exception as e:
+                # Since the tag creation was successful, we'll return 200.
+                return Response("Tag removed, but failed to create log entry: %s" % str(e),
+                     status=status.HTTP_200_OK)
 
             return Response("Tag deleted.",status=status.HTTP_200_OK)
         except:
diff --git a/gracedb/models.py b/gracedb/models.py
index 93fe079a0..df009db73 100644
--- a/gracedb/models.py
+++ b/gracedb/models.py
@@ -1,4 +1,4 @@
-from django.db import models
+from django.db import models, IntegrityError
 from django.core.urlresolvers import reverse
 
 from model_utils.managers import InheritanceManager
@@ -220,12 +220,27 @@ class EventLog(models.Model):
         return logset.index(self)
 
     def save(self, *args, **kwargs):
-        if self.event.eventlog_set.count():
-            self.N = event.eventlog_set.order_by('-N')[0].N + 1
-        else:
-            self.N = 1
-        # XXX This call to save should be inside a try/except.  Loop or something.
-        super(EventLog, self).save(*args, **kwargs)
+        success = False
+        attempts = 0
+        while (not success and attempts < 5):
+            attempts = attempts + 1
+            if self.event.eventlog_set.count():
+                self.N = event.eventlog_set.order_by('-N')[0].N + 1
+            else:
+                self.N = 1
+            try:
+                super(EventLog, self).save(*args, **kwargs)
+                success = True
+            except IntegrityError:
+                # IntegrityError means an attempt to insert a duplicate
+                # key or to violate a foreignkey constraint.
+                # We are under race conditions.  Let's try again.
+                pass
+
+        if not success:
+            # XXX Should this be a custom exception?  That way we could catch it
+            # in the views that use it and give an informative error message.
+            raise Exception("Too many attempts to save log message. Something is wrong.")
 
 class Labelling(models.Model):
     event = models.ForeignKey(Event)
diff --git a/gracedb/views.py b/gracedb/views.py
index b78030ca3..98497b827 100644
--- a/gracedb/views.py
+++ b/gracedb/views.py
@@ -121,7 +121,12 @@ def skyalert(request, graceid):
 
     if createLogEntry:
         logentry = EventLog(event=event, issuer=request.ligouser, comment=message)
-        logentry.save()
+        try:
+            logentry.save()
+        except:
+            # XXX Failed to create log entry for skyalert submission.
+            # Error message?
+            pass
 
     return HttpResponseRedirect(reverse(view, args=[graceid]))
 
@@ -298,13 +303,16 @@ def _createLog(request, graceid, comment, uploadedFile=None):
                 logEntry.filename = uploadedFile.name
             except Exception, e:
                 rdict['error'] = "Problem saving file: %s" % str(e)
-        logEntry.save()
+        try:
+            logEntry.save()
 
-        if request.POST.get('alert') == "True":
-            description = "LOG: "
-            if uploadedFile:
-                description = "UPLOAD: '%s' " % uploadedFile.name
-            issueAlertForUpdate(event, description+comment, doxmpp=True, filename=uploadedFile.name)
+            if request.POST.get('alert') == "True":
+                description = "LOG: "
+                if uploadedFile:
+                    description = "UPLOAD: '%s' " % uploadedFile.name
+                issueAlertForUpdate(event, description+comment, doxmpp=True, filename=uploadedFile.name)
+        except Exception, e:
+            rdict['error'] = "Failed to save log message: %s" % str(e) 
 
     # XXX should be json
     rval = str(rdict)
@@ -397,7 +405,11 @@ def create_label(graceid, labelName, creator, doAlert=True, doXMPP=True):
         labelling.save()
         message = "Label: %s" % label.name
         log = EventLog(event=event, issuer=creator, comment=message)
-        log.save()
+        try:       
+            log.save()
+        except Exception as e:
+            # XXX This looks a bit odd to me.
+            d['error'] = str(e)
 
         try:
             issueAlertForLabel(event, label, doXMPP)
@@ -451,7 +463,12 @@ def logentry(request, graceid, num=None):
         # create a log entry
         elog = EventLog(event=event, issuer=request.ligouser)
         elog.comment = request.POST.get('comment') or request.GET.get('comment')
-        elog.save()
+        try:
+            elog.save()
+        except Exception as e:
+            # XXX I feel like this should be a 500 error.  
+            return HttpResponse("ERROR: %s" % str(e))
+
         tagname = request.POST.get('tagname')
         if tagname:
             # Look for the tag.  If it doesn't already exist, create it.
@@ -469,7 +486,16 @@ def logentry(request, graceid, num=None):
             tlog = EventLog(event=event,
                                issuer=request.ligouser,
                                comment=msg)
-            tlog.save()
+            try:
+                tlog.save()
+            except Exception as e:
+                # XXX Maybe this isn't a big deal.  It's more of a 
+                # warning than an error.
+                msg = "Failed to save log entry to document tag:  "
+                msg = msg + str(e)
+                msg = msg + "\n However, the log message itself was saved."
+                return HttpResponse(msg)
+
     else:
         try:
             elog = event.eventlog_set.order_by('created').all()[int(num)]
@@ -1050,7 +1076,13 @@ def taglogentry(request, graceid, num, tagname):
             logentry = EventLog(event=event,
                                issuer=request.ligouser,
                                comment=msg)
-            logentry.save()
+            try:
+                logentry.save()
+            except Exception as e:
+                msg = "Failed to save log entry documenting tag:  "
+                msg = msg + str(e) + '\n'
+                msg = "The tag itself, however, is saved."
+                return HttpResponse(msg, content_type="text")
     else:
         # We will only allow PUT here.  Anything else is a bad request: 400
         return HttpResponseBadRequest
-- 
GitLab