diff --git a/config/settings/base.py b/config/settings/base.py index bde2e44cb0c783e07d4c91a91b3bc099d4579f64..33149a3386bddd516e9c1e6f2c0f2b8979769ab1 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -26,7 +26,7 @@ MAINTENANCE_MODE = False MAINTENANCE_MODE_MESSAGE = None # Version --------------------------------------------------------------------- -PROJECT_VERSION = '2.8.1-2' +PROJECT_VERSION = '2.8.3' # Unauthenticated access ------------------------------------------------------ # This variable should eventually control whether unauthenticated access is @@ -198,6 +198,8 @@ COINC_PIPELINES = [ GRB_PIPELINES = [ 'Fermi', 'Swift', + 'INTEGRAL', + 'AGILE', ] # VOEvent stream -------------------------------------------------------------- @@ -386,8 +388,8 @@ REST_FRAMEWORK = { ), 'DEFAULT_THROTTLE_RATES': { 'anon_burst': '300/minute', - 'event_creation': '10/second', - 'annotation' : '10/second', + 'event_creation': '25/second', + 'annotation' : '25/second', }, 'DEFAULT_AUTHENTICATION_CLASSES': ( 'api.backends.GraceDbAuthenticatedAuthentication', diff --git a/config/settings/container/base.py b/config/settings/container/base.py index c679bb80cfe8439565dc5d9699c8d7ec4b8da916..97c75a62c263e164781d67e6f9498a3608370647 100644 --- a/config/settings/container/base.py +++ b/config/settings/container/base.py @@ -118,6 +118,7 @@ MIDDLEWARE = [ #} + # Priority server settings ---------------------------------------------------- PRIORITY_SERVER = False is_priority_server = get_from_env('DJANGO_PRIORITY_SERVER', None, @@ -152,6 +153,64 @@ DATABASES = { } } + +# Adding a fun conditional to control Amazon AWS Elasticache settings. +# Here's the logic: +# 1) check for the existence of the DJANGO_AWS_ELASTICACHE_ADDR address +# variable. If it's present, then load the CACHE settings and MIDDLEWARE +# settings required for AWS elasticache'ing +# +# 2) Check for the existence of DJANGO_AWS_ELASTICACHE_TIMEOUT variable. This +# will control the cache timeout. If it's not set, default to 30s. +# +# 3) If not, default to old cache settings, which is effectively no-caches. + +try: + AWS_ELASTICACHE_ADDR = get_from_env('DJANGO_AWS_ELASTICACHE_ADDR') + + # I *think* if the variable isn't set, then that should raise an exception + # and then it should skip the rest: + + try: + AWS_ELASTICACHE_TIMEOUT = get_from_env('DJANGO_AWS_ELASTICACHE_TIMEOUT') + except: + AWS_ELASTICACHE_TIMEOUT = 30 + + # Load modified caching middleware: + MIDDLEWARE = [ + 'core.middleware.maintenance.MaintenanceModeMiddleware', + 'events.middleware.PerformanceMiddleware', + 'core.middleware.accept.AcceptMiddleware', + 'core.middleware.api.ClientVersionMiddleware', + 'core.middleware.api.CliExceptionMiddleware', + 'django.middleware.cache.UpdateCacheMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', + 'core.middleware.proxy.XForwardedForMiddleware', + 'user_sessions.middleware.SessionMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'ligoauth.middleware.ShibbolethWebAuthMiddleware', + 'ligoauth.middleware.ControlRoomMiddleware', + ] + + # Set caches: + CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': AWS_ELASTICACHE_ADDR, + 'TIMEOUT': AWS_ELASTICACHE_TIMEOUT, + 'KEY_PREFIX': 'NULL', + }, + # For API throttles + 'throttles': { + 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', + 'LOCATION': 'api_throttle_cache', # Table name + }, + } +except: + pass + # Main server "hostname" - a little hacky but OK SERVER_HOSTNAME = SERVER_FQDN.split('.')[0] diff --git a/config/settings/container/dev.py b/config/settings/container/dev.py index 79154c6fce1cbe6d862c4e9853fca2f63c63c098..b3ce05c527af22d7433b5277eaeceb3231d81e95 100644 --- a/config/settings/container/dev.py +++ b/config/settings/container/dev.py @@ -89,19 +89,5 @@ Events and associated data may change or be removed at any time. </ul> """ -# AWS Elasticache settings: -AWS_ELASTICACHE_ADDR = get_from_env('DJANGO_AWS_ELASTICACHE_ADDR') - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': AWS_ELASTICACHE_ADDR, - 'TIMEOUT': 30, - 'KEY_PREFIX': "1", - }, - # For API throttles - 'throttles': { - 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - 'LOCATION': 'api_throttle_cache', # Table name - }, -} +if AWS_ELASTICACHE_ADDR: + CACHES['default']['KEY_PREFIX'] = '1' diff --git a/config/settings/container/playground.py b/config/settings/container/playground.py index 3f2714036d8b39c2c51e48c30f69c870f9bffdd1..6ff537127263032d87ca78251a7a803cd9820266 100644 --- a/config/settings/container/playground.py +++ b/config/settings/container/playground.py @@ -43,19 +43,6 @@ A nightly cron job removes events older than 21 days.</li> if (DEBUG == True): raise RuntimeError("Turn off debug mode for playground") -# AWS Elasticache settings: -AWS_ELASTICACHE_ADDR = get_from_env('DJANGO_AWS_ELASTICACHE_ADDR') - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': AWS_ELASTICACHE_ADDR, - 'TIMEOUT': 30, - 'KEY_PREFIX': '3', - }, - # For API throttles - 'throttles': { - 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - 'LOCATION': 'api_throttle_cache', # Table name - }, -} +# Set elasticache prefix if the correct variables are set. +if AWS_ELASTICACHE_ADDR: + CACHES['default']['KEY_PREFIX'] = '3' diff --git a/config/settings/container/production.py b/config/settings/container/production.py index 1e1b50e060078a4961d0542088fc0ebf7ca41502..01892e2ca5167b3f0ca3ec000a2533b32500d7fe 100644 --- a/config/settings/container/production.py +++ b/config/settings/container/production.py @@ -61,3 +61,8 @@ if sentry_dsn is not None: # Safety check on debug mode for production if (DEBUG == True): raise RuntimeError("Turn off debug mode for production") + + +# Set elasticache prefix if the correct variables are set. +if AWS_ELASTICACHE_ADDR: + CACHES['default']['KEY_PREFIX'] = '4' diff --git a/config/settings/container/test.py b/config/settings/container/test.py index f2731b0b25130065f8887dd51b220e7431294b68..a03510bf7b4ae06cb1a480b557bdaf4cd9be675a 100644 --- a/config/settings/container/test.py +++ b/config/settings/container/test.py @@ -89,19 +89,5 @@ to Playground or Production. Note, on this GraceDB instance: </ul> """ -# AWS Elasticache settings: -AWS_ELASTICACHE_ADDR = get_from_env('DJANGO_AWS_ELASTICACHE_ADDR') - -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': AWS_ELASTICACHE_ADDR, - 'TIMEOUT': 30, - 'KEY_PREFIX': '2', - }, - # For API throttles - 'throttles': { - 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - 'LOCATION': 'api_throttle_cache', # Table name - }, -} +if AWS_ELASTICACHE_ADDR: + CACHES['default']['KEY_PREFIX'] = '2' diff --git a/docs/user_docs/source/models.rst b/docs/user_docs/source/models.rst index d3f279a43545a8760e34879f44f62275004c7ed3..9ca6e5852a317dd6e2131ca2f63bea90280cbe8f 100644 --- a/docs/user_docs/source/models.rst +++ b/docs/user_docs/source/models.rst @@ -12,7 +12,7 @@ The different types of events in GraceDB are distinguished by the following para - ``Group``: the working group responsible for finding the candidate - values: ``CBC``, ``Burst``, ``External``, ``Test`` - ``Pipeline``: the data analysis software tool used make the detection - - values: ``MBTAOnline``, ``CWB``, ``CWB2G``, ``gstlal``, ``spiir``, ``HardwareInjection``, ``Fermi``, ``Swift``, ``SNEWS``, ``oLIB`` + - values: ``MBTAOnline``, ``CWB``, ``CWB2G``, ``gstlal``, ``spiir``, ``HardwareInjection``, ``Fermi``, ``Swift``, ``INTEGRAL``, ``AGILE``, ``SNEWS``, ``oLIB`` - ``Search``: the search activity which led to the detection - values: ``AllSky``, ``AllSkyLong``, ``LowMass``, ``HighMass``, ``GRB``, ``Supernova``, ``MDC``, ``BBH`` diff --git a/docs/user_docs/source/rest.rst b/docs/user_docs/source/rest.rst index 3f4157fecb2a8e75c4698acee1a3b6d071b8cc5f..e2328887b61b453f941d57de2d01761504864880 100644 --- a/docs/user_docs/source/rest.rst +++ b/docs/user_docs/source/rest.rst @@ -43,7 +43,7 @@ Using the API programmatically ============================== At present, the GraceDB API is not well-documented, and it may be challenging to write a custom client for accessing it. The LSC maintains a Python package, ligo-gracedb, for simplifying access to the API. -This package's `documentation <https://gw.readthedocs.io/ligo-gracedb>`__ is comprehensive and includes basic installation, configuration, and usage. +This package's `documentation <https://ligo-gracedb.readthedocs.io/en/latest/>`__ is comprehensive and includes basic installation, configuration, and usage. For non-Python users, ligo-gracedb also comes with a command-line client. diff --git a/gracedb/alerts/xmpp.py b/gracedb/alerts/xmpp.py index 09445b45690ee1d802422f57edbc984f5c5fec57..1e5521c0b2b2089959f162c7efcf172c1a8008b6 100644 --- a/gracedb/alerts/xmpp.py +++ b/gracedb/alerts/xmpp.py @@ -8,6 +8,7 @@ import sys from django.core.mail import EmailMessage from django.conf import settings +from xml.sax.saxutils import escape from core.time_utils import gpsToUtc from events.permission_utils import is_external @@ -92,6 +93,9 @@ def issue_xmpp_alerts(event_or_superevent, alert_type, serialized_object, # Dump to JSON format: # simplejson.dumps is needed to properly handle Decimal fields msg = simplejson.dumps(lva_data) + + # Try 'escaping' the message: + msg = escape(msg) # Log message for debugging logger.info("issue_xmpp_alerts: sending message {msg} for {uid}" \ diff --git a/gracedb/events/migrations/0045_add_early_warning_search.py b/gracedb/events/migrations/0045_add_early_warning_search.py new file mode 100644 index 0000000000000000000000000000000000000000..87360f41c53d0f266f3d36e948521cc655d6a053 --- /dev/null +++ b/gracedb/events/migrations/0045_add_early_warning_search.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-25 19:16 +from __future__ import unicode_literals + +from django.db import migrations + +# Creates initial search instances + +# Search names and descriptions +SEARCH_TYPES = [ + {'name': 'EarlyWarning', 'description': 'GstLAL Early Warning Search'}, +] + +def add_searches(apps, schema_editor): + Search = apps.get_model('events', 'Search') + + # Create searches + for search_dict in SEARCH_TYPES: + search, created = Search.objects.get_or_create(name=search_dict['name']) + if 'description' in search_dict: + search.description = search_dict['description'] + search.save() + +def remove_searches(apps, schema_editor): + Search = apps.get_model('events', 'Search') + + # Delete searches + Search.objects.filter(name__in=[s['name'] for s in SEARCH_TYPES]).delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0044_add_skymap_labels'), + ] + + operations = [ + migrations.RunPython(add_searches, remove_searches), + ] diff --git a/gracedb/events/migrations/0046_add_integral_agile_pipelines.py b/gracedb/events/migrations/0046_add_integral_agile_pipelines.py new file mode 100644 index 0000000000000000000000000000000000000000..ca043f920cfb4e5335d9af4fa2e44ce534bcb6bc --- /dev/null +++ b/gracedb/events/migrations/0046_add_integral_agile_pipelines.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-25 19:16 +from __future__ import unicode_literals + +from django.db import migrations + +# Creates initial search pipeline instances + +# List of search pipeline names +NEW_GRB_PIPELINES = [ + 'INTEGRAL', + 'AGILE', +] + +def add_pipelines(apps, schema_editor): + Pipeline = apps.get_model('events', 'Pipeline') + + # Create pipelines + for pipeline_name in NEW_GRB_PIPELINES: + pipeline, created = Pipeline.objects.get_or_create(name=pipeline_name) + +def remove_pipelines(apps, schema_editor): + Pipeline = apps.get_model('events', 'Pipeline') + + # Delete pipelines + Pipeline.objects.filter(name__in=SEARCH_PIPELINES).delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0045_add_early_warning_search'), + ] + + operations = [ + migrations.RunPython(add_pipelines, remove_pipelines), + ] diff --git a/gracedb/events/migrations/0047_specify_integralagile_pipeline_types.py b/gracedb/events/migrations/0047_specify_integralagile_pipeline_types.py new file mode 100644 index 0000000000000000000000000000000000000000..743e0a2b657e3c1f7d2bf036db9ad982400905bd --- /dev/null +++ b/gracedb/events/migrations/0047_specify_integralagile_pipeline_types.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-07-10 19:34 +from __future__ import unicode_literals + +from django.db import migrations + +from events.models import Pipeline as pipeline_model + +PIPELINES = [ + ('INTEGRAL', pipeline_model.PIPELINE_TYPE_EXTERNAL), + ('AGILE', pipeline_model.PIPELINE_TYPE_EXTERNAL), +] + +DEFAULT_PIPELINE_TYPE = pipeline_model.PIPELINE_TYPE_OTHER + +def update_pipeline_types(apps, schema_editor): + Pipeline = apps.get_model('events', 'Pipeline') + + for p_tuple in PIPELINES: + p = Pipeline.objects.get(name=p_tuple[0]) + p.pipeline_type = p_tuple[1] + p.save(update_fields=['pipeline_type']) + + +def revert_pipeline_types(apps, schema_editor): + Pipeline = apps.get_model('events', 'Pipeline') + + for p_tuple in PIPELINES: + p = Pipeline.objects.get(name=p_tuple[0]) + p.pipeline_type = DEFAULT_PIPELINE_TYPE + p.save(update_fields=['pipeline_type']) + + +class Migration(migrations.Migration): + + dependencies = [ + ('events', '0046_add_integral_agile_pipelines'), + ] + + operations = [ + migrations.RunPython(update_pipeline_types, revert_pipeline_types), + ] diff --git a/gracedb/events/translator.py b/gracedb/events/translator.py index 7aa442be7e56c46f0e4f720fbf8c5a8fdebe8ac9..dfaba25be5979de0c8ce5bf380be20e98af17bd0 100644 --- a/gracedb/events/translator.py +++ b/gracedb/events/translator.py @@ -333,7 +333,7 @@ def handle_uploaded_data(event, datafilename, comment=comment) log.save() - elif pipeline in ['Swift', 'Fermi', 'SNEWS']: + elif pipeline in ['Swift', 'Fermi', 'SNEWS', 'INTEGRAL','AGILE']: # Get the event time from the VOEvent file error = None populateGrbEventFromVOEventFile(datafilename, event) diff --git a/gracedb/events/view_logic.py b/gracedb/events/view_logic.py index 5520b39d39f81678df6e1c98f442c9a3dad2c2c1..32855feca4a5fa35b9f22158275123b13c5d5336 100644 --- a/gracedb/events/view_logic.py +++ b/gracedb/events/view_logic.py @@ -55,7 +55,7 @@ def _createEventFromForm(request, form): # Create Event if pipeline.name in ['gstlal', 'spiir', 'MBTAOnline', 'pycbc',]: event = CoincInspiralEvent() - elif pipeline.name in ['Fermi', 'Swift', 'SNEWS']: + elif pipeline.name in ['Fermi', 'Swift', 'SNEWS','INTEGRAL','AGILE']: event = GrbEvent() elif pipeline.name in ['CWB', 'CWB2G']: event = MultiBurstEvent() diff --git a/gracedb/migrations/guardian/0009_populate_integralagile_uploaders.py b/gracedb/migrations/guardian/0009_populate_integralagile_uploaders.py new file mode 100644 index 0000000000000000000000000000000000000000..9cc0841fa6b615fd962c19695665cf2f93f2c9a8 --- /dev/null +++ b/gracedb/migrations/guardian/0009_populate_integralagile_uploaders.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-11-01 16:19 +from __future__ import unicode_literals + +from django.db import migrations + +# Creates UserObjectPermission objects which allow specific users +# to add events for pipelines. Based on current production database +# content (27 October 2017) + +# List of pipeline names and lists of usernames who should +# be allowed to add events for them +PP_LIST = [ + { + 'pipeline': 'INTEGRAL', + 'usernames': [ + 'brandon.piotrzkowski@LIGO.org', + 'alexander.urban@LIGO.ORG', + 'grb.exttrig', + 'emfollow', + ] + }, + { + 'pipeline': 'AGILE', + 'usernames': [ + 'brandon.piotrzkowski@LIGO.org', + 'alexander.urban@LIGO.ORG', + 'grb.exttrig', + 'emfollow', + ] + }, +] + +def add_permissions(apps, schema_editor): + User = apps.get_model('auth', 'User') + Permission = apps.get_model('auth', 'Permission') + UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission') + Pipeline = apps.get_model('events', 'Pipeline') + ContentType = apps.get_model('contenttypes', 'ContentType') + + perm = Permission.objects.get(codename='populate_pipeline') + ctype = ContentType.objects.get_for_model(Pipeline) + for pp_dict in PP_LIST: + pipeline = Pipeline.objects.get(name=pp_dict['pipeline']) + + # Loop over users + for username in pp_dict['usernames']: + + # Robot users should have been already created by ligoauth 0003, + # but we have to create human user accounts here + user, _ = User.objects.get_or_create(username=username) + + # Create UserObjectPermission + uop, uop_created = UserObjectPermission.objects.get_or_create( + user=user, permission=perm, content_type=ctype, + object_pk=pipeline.id) + +def remove_permissions(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0008_add_edoardo_milotti_to_cwb'), + ('events', '0046_add_integral_agile_pipelines'), + ] + + operations = [ + migrations.RunPython(add_permissions, remove_permissions), + ] diff --git a/gracedb/migrations/guardian/0010_authorize_raven_users_to_populate_new_pipelines.py b/gracedb/migrations/guardian/0010_authorize_raven_users_to_populate_new_pipelines.py new file mode 100644 index 0000000000000000000000000000000000000000..726e2b571aa280ffc40bccb42e483e98ed9aefdd --- /dev/null +++ b/gracedb/migrations/guardian/0010_authorize_raven_users_to_populate_new_pipelines.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2019-05-08 16:27 +from __future__ import unicode_literals + +from django.db import migrations + +GROUP_NAME = 'raven_users' +PIPELINES = [ + 'INTEGRAL', + 'AGILE', +] + + +def add_permissions(apps, schema_editor): + Group = apps.get_model('auth', 'Group') + Permission = apps.get_model('auth', 'Permission') + GroupObjectPermission = apps.get_model('guardian', 'GroupObjectPermission') + Pipeline = apps.get_model('events', 'Pipeline') + ContentType = apps.get_model('contenttypes', 'ContentType') + + # Get group + group = Group.objects.get(name=GROUP_NAME) + + perm = Permission.objects.get(codename='populate_pipeline') + ctype = ContentType.objects.get_for_model(Pipeline) + for pipeline in PIPELINES: + pipeline = Pipeline.objects.get(name=pipeline) + + # Create GroupObjectPermission + gop = GroupObjectPermission.objects.create(group=group, + permission=perm, content_type=ctype, object_pk=pipeline.id) + + +def remove_permissions(apps, schema_editor): + Group = apps.get_model('auth', 'Group') + Permission = apps.get_model('auth', 'Permission') + GroupObjectPermission = apps.get_model('guardian', 'GroupObjectPermission') + Pipeline = apps.get_model('events', 'Pipeline') + ContentType = apps.get_model('contenttypes', 'ContentType') + + # Get group + group = Group.objects.get(name=GROUP_NAME) + + perm = Permission.objects.get(codename='populate_pipeline') + ctype = ContentType.objects.get_for_model(Pipeline) + for pipeline in PIPELINES: + pipeline = Pipeline.objects.get(name=pipeline) + + # Get GroupObjectPermission and delete + gop = GroupObjectPermission.objects.get(group=group, + permission=perm, content_type=ctype, object_pk=pipeline.id) + gop.delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('guardian', '0009_populate_integralagile_uploaders'), + ] + + operations = [ + migrations.RunPython(add_permissions, remove_permissions), + ]