diff --git a/gracedb/fixtures/test_perms/cbc-lm.xml b/gracedb/fixtures/test_perms/cbc-lm.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d04abd4816a6cc6b3af128ddca78cbc1873fee4
--- /dev/null
+++ b/gracedb/fixtures/test_perms/cbc-lm.xml
@@ -0,0 +1,112 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!DOCTYPE LIGO_LW SYSTEM "http://ldas-sw.ligo.caltech.edu/doc/ligolwAPI/html/ligolw_dtd.txt">
+<LIGO_LW>
+	<Table Name="sngl_inspiral:table">
+		<Column Type="real_4" Name="sngl_inspiral:cont_chisq"/>
+		<Column Type="real_4" Name="sngl_inspiral:bank_chisq"/>
+		<Column Type="int_4s" Name="sngl_inspiral:chisq_dof"/>
+		<Column Type="real_8" Name="sngl_inspiral:end_time_gmst"/>
+		<Column Type="real_8" Name="sngl_inspiral:event_duration"/>
+		<Column Type="real_4" Name="sngl_inspiral:chisq"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha"/>
+		<Column Type="real_4" Name="sngl_inspiral:coa_phase"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha2"/>
+		<Column Type="real_4" Name="sngl_inspiral:mchirp"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha1"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha6"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha4"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha5"/>
+		<Column Type="ilwd:char" Name="sngl_inspiral:event_id"/>
+		<Column Type="real_4" Name="sngl_inspiral:chi"/>
+		<Column Type="int_4s" Name="sngl_inspiral:cont_chisq_dof"/>
+		<Column Type="real_4" Name="sngl_inspiral:tau2"/>
+		<Column Type="real_4" Name="sngl_inspiral:tau3"/>
+		<Column Type="real_4" Name="sngl_inspiral:tau0"/>
+		<Column Type="real_4" Name="sngl_inspiral:tau4"/>
+		<Column Type="real_4" Name="sngl_inspiral:tau5"/>
+		<Column Type="real_8" Name="sngl_inspiral:template_duration"/>
+		<Column Type="int_4s" Name="sngl_inspiral:impulse_time"/>
+		<Column Type="int_4s" Name="sngl_inspiral:impulse_time_ns"/>
+		<Column Type="real_4" Name="sngl_inspiral:rsqveto_duration"/>
+		<Column Type="lstring" Name="sngl_inspiral:channel"/>
+		<Column Type="real_4" Name="sngl_inspiral:mtotal"/>
+		<Column Type="real_4" Name="sngl_inspiral:alpha3"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma5"/>
+		<Column Type="real_4" Name="sngl_inspiral:f_final"/>
+		<Column Type="real_4" Name="sngl_inspiral:beta"/>
+		<Column Type="ilwd:char" Name="sngl_inspiral:process_id"/>
+		<Column Type="real_4" Name="sngl_inspiral:snr"/>
+		<Column Type="int_4s" Name="sngl_inspiral:bank_chisq_dof"/>
+		<Column Type="real_4" Name="sngl_inspiral:kappa"/>
+		<Column Type="real_4" Name="sngl_inspiral:eff_distance"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma7"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma6"/>
+		<Column Type="lstring" Name="sngl_inspiral:search"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma4"/>
+		<Column Type="real_4" Name="sngl_inspiral:mass1"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma2"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma1"/>
+		<Column Type="real_4" Name="sngl_inspiral:mass2"/>
+		<Column Type="real_4" Name="sngl_inspiral:ttotal"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma0"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma9"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma8"/>
+		<Column Type="real_4" Name="sngl_inspiral:Gamma3"/>
+		<Column Type="real_4" Name="sngl_inspiral:eta"/>
+		<Column Type="real_4" Name="sngl_inspiral:psi0"/>
+		<Column Type="int_4s" Name="sngl_inspiral:end_time"/>
+		<Column Type="real_4" Name="sngl_inspiral:amplitude"/>
+		<Column Type="real_4" Name="sngl_inspiral:psi3"/>
+		<Column Type="int_4s" Name="sngl_inspiral:end_time_ns"/>
+		<Column Type="lstring" Name="sngl_inspiral:ifo"/>
+		<Column Type="real_8" Name="sngl_inspiral:sigmasq"/>
+		<Stream Name="sngl_inspiral:table" Type="Local" Delimiter=",">
+			111117.7,46.05555,0,13.37498739211096,0.000244140625,0,0,-2.222122,0,1.161378,0,0,0,0,"sngl_inspiral:event_id:0",0,112837,1.459517,0.9047917,26.94421,0.04901026,0,27.54793872301135,971609248,861816406,0,"DMT-STRAIN",2.698323,0,426.13315,1629.595,0,"process:process_id:0",5.561541,15,0,57.40068,0,0,"FindChirpSPtwoPN",-212.24567,1.533016,7010.147,-2963.2476,1.165307,27.54794,174532.62,0,0,115.9822,0.2453574,0,971609248,0,0,861816406,"H1",101911.765625,
+			109975.2,32.12224,0,13.37498868415753,0.000244140625,0,0,-2.353283,0,1.15963,0,0,0,0,"sngl_inspiral:event_id:1",0,112935,1.504488,0.9970195,27.01193,0.05250296,0,27.57190592010491,971609248,866455078,0,"DMT-STRAIN",2.965915,0,386.59695,1482.569,0,"process:process_id:0",5.63967,15,0,49.10112,0,0,"FindChirpSPtwoPN",-201.89705,2.083082,6643.8418,-2951.074,0.8828334,27.57191,170850.05,0,0,115.80309,0.2090584,0,971609248,0,0,866455078,"L1",76681.4296875
+		</Stream>
+	</Table>
+	<Table Name="coinc_inspiral:table">
+		<Column Type="real_8" Name="coinc_inspiral:false_alarm_rate"/>
+		<Column Type="real_8" Name="coinc_inspiral:mchirp"/>
+		<Column Type="ilwd:char" Name="coinc_inspiral:coinc_event_id"/>
+		<Column Type="real_8" Name="coinc_inspiral:combined_far"/>
+		<Column Type="real_8" Name="coinc_inspiral:mass"/>
+		<Column Type="int_4s" Name="coinc_inspiral:end_time"/>
+		<Column Type="real_8" Name="coinc_inspiral:snr"/>
+		<Column Type="int_4s" Name="coinc_inspiral:end_time_ns"/>
+		<Column Type="lstring" Name="coinc_inspiral:ifos"/>
+		<Stream Name="coinc_inspiral:table" Type="Local" Delimiter=",">
+			2.075093374861122,1.160504,"coinc_event:coinc_event_id:0",2.075093374861122,2.8321192,971609248,7.121797825164989,861816406,"H1,L1"
+		</Stream>
+	</Table>
+	<Table Name="coinc_definer:table">
+		<Column Type="lstring" Name="coinc_definer:search"/>
+		<Column Type="lstring" Name="coinc_definer:description"/>
+		<Column Type="ilwd:char" Name="coinc_definer:coinc_def_id"/>
+		<Column Type="int_4u" Name="coinc_definer:search_coinc_type"/>
+		<Stream Name="coinc_definer:table" Type="Local" Delimiter=",">
+			"inspiral","sngl_inspiral&lt;--&gt;sngl_inspiral coincidences","coinc_definer:coinc_def_id:1",0
+		</Stream>
+	</Table>
+	<Table Name="coinc_event:table">
+		<Column Type="ilwd:char" Name="coinc_event:coinc_event_id"/>
+		<Column Type="lstring" Name="coinc_event:instruments"/>
+		<Column Type="int_4u" Name="coinc_event:nevents"/>
+		<Column Type="ilwd:char" Name="coinc_event:process_id"/>
+		<Column Type="ilwd:char" Name="coinc_event:coinc_def_id"/>
+		<Column Type="ilwd:char" Name="coinc_event:time_slide_id"/>
+		<Column Type="real_8" Name="coinc_event:likelihood"/>
+		<Stream Name="coinc_event:table" Type="Local" Delimiter=",">
+			"coinc_event:coinc_event_id:0","H1,L1",3,"process:process_id:2","coinc_definer:coinc_def_id:1",,,
+		</Stream>
+	</Table>
+	<Table Name="coinc_event_map:table">
+		<Column Type="ilwd:char" Name="coinc_event_map:event_id"/>
+		<Column Type="char_v" Name="coinc_event_map:table_name"/>
+		<Column Type="ilwd:char" Name="coinc_event_map:coinc_event_id"/>
+		<Stream Name="coinc_event_map:table" Type="Local" Delimiter=",">
+			"sngl_inspiral:event_id:0","sngl_inspiral","coinc_event:coinc_event_id:0",
+			"sngl_inspiral:event_id:1","sngl_inspiral","coinc_event:coinc_event_id:0"
+		</Stream>
+	</Table>
+</LIGO_LW>
diff --git a/gracedb/test/test_perms.py b/gracedb/test/test_perms.py
index a076f399bf9220991d74cbe6213269309bf3acf8..9f82395fecbacaf4edd5c1be1a78d8114aef7892 100644
--- a/gracedb/test/test_perms.py
+++ b/gracedb/test/test_perms.py
@@ -1,4 +1,5 @@
 from django.test import TestCase
+from django.test.utils import override_settings
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth.models import Permission, Group, User
@@ -6,7 +7,11 @@ from guardian.models import GroupObjectPermission
 from gracedb.models import Event, GrbEvent, CoincInspiralEvent
 from gracedb.models import MultiBurstEvent
 
+from django.conf import settings
+
 import json
+import os
+import shutil
 from urllib import urlencode
     
 #-------------------------------------------------------------------------------
@@ -14,6 +19,7 @@ from urllib import urlencode
 # Some utilities
 #-------------------------------------------------------------------------------
 #-------------------------------------------------------------------------------
+TMP_DATA_DIR = '/tmp/test_perms_data'
 
 def get_user(category):
     if category=='public':
@@ -24,7 +30,11 @@ def get_user(category):
         return User.objects.get(first_name='Albert', last_name='Einstein')
     elif category=='exec':
         return User.objects.get(first_name='Spokesy', last_name='McSpokesperson')         
-
+    elif category=='gstlal_submitter':
+        return User.objects.get(last_name='GstLal CBC')
+    else:
+        return None
+    
 def get_public_coinc_event():
     ctype = ContentType.objects.get(model='CoincInspiralEvent')
     perm  = Permission.objects.get(codename='view_coincinspiralevent')
@@ -60,6 +70,27 @@ def get_internal_coinc_event():
             break
     return e
 
+# Given a Django test client, attempt to create a CBC, gstlal, 
+# LowMass event. 
+EVENT_FILE = os.path.join(settings.ROOT_PATH,'gracedb/fixtures/test_perms/cbc-lm.xml')
+
+def request_event_creation(client, username):
+    event_file = open(EVENT_FILE,'r')
+    url = '/events/create/'
+    input_dict = {
+        'group'      : 'CBC',
+        'pipeline'   : 'gstlal',
+        'search'     : 'LM',
+        'eventFile'  : event_file,
+    }
+    return client.post(url, input_dict, REMOTE_USER=username)
+
+#-------------------------------------------------------------------------------
+#-------------------------------------------------------------------------------
+# Test Perms Class
+#-------------------------------------------------------------------------------
+#-------------------------------------------------------------------------------
+
 class TestPerms(TestCase): 
     # I wonder if the order of loading the fixtures will matter?
     fixtures = [
@@ -159,6 +190,14 @@ class TestPerms(TestCase):
         for e in Event.objects.all():
             e.refresh_perms()
 
+        # Lastly, let's create a temporary data dir. 
+        if not os.path.isdir(TMP_DATA_DIR):
+            os.mkdir(TMP_DATA_DIR)
+
+    def tearDown(self):
+        # Get rid of that temporary data dir.
+        shutil.rmtree(TMP_DATA_DIR)
+
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
     # Tests of view access
@@ -255,12 +294,92 @@ class TestPerms(TestCase):
     # Tests of event annotation
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
-    
-    # Test annotation of events by public users
+
+    # What annotation activities need to be tested?
+    # - EventLog creation
+    #   /events/GXXXX/log/
+    #   POST dict keys: comment, tagname  (no files through web interface)
+    # - Tag creation
+    #   /events/GXXXX/log/N/tag/<tagname>
+    #   POST dict keys: displayName
+    #   test DELETE?
+    # - Labelling
+    #   no way to do this through the web interface
+
+    # Test annotation of events user.
+    def test_public_log_creation(self):
+        # Choose any event. The public coinc one will do.
+        event = get_public_coinc_event()
+        url = '/events/%s/log/' % event.graceid()
+        input_dict = {
+            'comment' : 'This is a test.',
+            'tagname' : 'test_tag',
+        }
+        response = self.client.post(url,input_dict,
+            REMOTE_USER=get_user('public').username)
+        self.assertEqual(response.status_code, 403)
+
+    def test_public_log_tagging(self):
+        # Choose any event. The public coinc one will do.
+        event = get_public_coinc_event()
+        # Try to add 'test_tag' to the first log entry.
+        url = '/events/%s/log/1/tag/test_tag' % event.graceid()
+        input_dict = {'displayName' : None,}
+        response = self.client.post(url, input_dict,
+            REMOTE_USER=get_user('public').username)
+        self.assertEqual(response.status_code, 403)
 
     # Test annotation of events by LV-EM users
+    def test_lvem_log_creation(self):
+        # Should be able to annotate the public event, but no others
+        public_coinc_event = get_public_coinc_event()
+        for e in CoincInspiralEvent.objects.all():
+            url = '/events/%s/log/' % e.graceid()
+            input_dict = {
+                'comment' : 'This is a test.',
+                'tagname' : 'test_tag',
+            }
+            response = self.client.post(url,input_dict,
+                REMOTE_USER=get_user('lvem').username)
+            if e.id==public_coinc_event.id:
+                # Not an AJAX call, so redirects to event page if successful. 
+                self.assertEqual(response.status_code, 302)
+            else:
+                self.assertEqual(response.status_code, 403)
+
+    def test_lvem_log_tagging(self):
+        public_coinc_event = get_public_coinc_event()
+        for e in CoincInspiralEvent.objects.all():
+            # Try to add 'test_tag' to the first log entry.
+            url = '/events/%s/log/1/tag/test_tag' % e.graceid()
+            input_dict = {'displayName' : None,}
+            response = self.client.post(url, input_dict,
+                REMOTE_USER=get_user('lvem').username)
+            if e.id==public_coinc_event.id:
+                self.assertEqual(response.status_code, 302)
+            else:
+                self.assertEqual(response.status_code, 403)
 
     # Test annotation of events by LIGO users
+    def test_internal_log_creation(self):
+        for e in CoincInspiralEvent.objects.all():
+            url = '/events/%s/log/' % e.graceid()
+            input_dict = {
+                'comment' : 'This is a test.',
+                'tagname' : 'test_tag',
+            }
+            response = self.client.post(url,input_dict,
+                REMOTE_USER=get_user('internal').username)
+            self.assertEqual(response.status_code, 302)
+
+    def test_internal_log_tagging(self):
+        for e in CoincInspiralEvent.objects.all():
+            # Try to add 'test_tag' to the first log entry.
+            url = '/events/%s/log/1/tag/test_tag' % e.graceid()
+            input_dict = {'displayName' : None,}
+            response = self.client.post(url, input_dict,
+                REMOTE_USER=get_user('internal').username)
+            self.assertEqual(response.status_code, 302)
 
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
@@ -268,10 +387,37 @@ class TestPerms(TestCase):
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
 
+    @override_settings(GRACEDB_DATA_DIR=TMP_DATA_DIR)
+    def test_event_creation(self):
+        gstlal_submitter = get_user('gstlal_submitter')
+        for user in User.objects.all():
+            response = request_event_creation(self.client, user.username)
+            if user.id==gstlal_submitter.id:
+                self.assertEqual(response.status_code, 200)
+            else:
+                self.assertEqual(response.status_code, 403)
+
+#    # Actually, you can only replace an event that you yourself created.
+#    # Thus, not sure if we really need this.
+#    def test_event_replacement(self):
+#        pass
+
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
     # Test changes to permissions
     #-------------------------------------------------------------------------------
     #-------------------------------------------------------------------------------
    
-
+    def test_perm_creation(self):
+        for user in User.objects.all():
+            # choose any event
+            event = CoincInspiralEvent.objects.all()[0]
+            # try POST to permission creation URL
+            url = '/events/%s/perms' % event.graceid() 
+            input_dict = {}
+            response = self.client.post(url, input_dict, REMOTE_USER=user.username)
+            groups = [g.name for g in user.groups.all()]
+            if not 'executives' in groups:
+                self.assertEqual(response.status_code, 403)
+            else:
+                self.assertEqual(response.status_code, 200)